var _STR = (function () {
    if ("scrollRestoration" in history) {
        history.scrollRestoration = "manual";
    }

    function addClass(classList, classStr) {
        var cl = classStr.split(/\s+/g);
        cl.forEach(function (className) {
            classList.add(className);
        })
    }

    function removeClass(classList, classStr) {
        var cl = classStr.split(/\s+/g);
        cl.forEach(function (className) {
            classList.remove(className);
        })
    }

    function isObject(obj) {
        return Object.prototype.toString.call(obj) === "[object Object]";
    }

    function getBaseUrl(urlStr) {
        if (typeof __dirname === "string") {
            urlStr = require("path").join(__dirname, urlStr);
            return urlStr.split("\\").slice(0, -1).join("\\") + "\\";
        }
        return urlStr.split("/").slice(0, -1).join("/") + "/";
    }

    function looseEqual(a, b) {
        if (a === b) {
            return true
        }
        var isObjectA = isObject(a);
        var isObjectB = isObject(b);
        if (isObjectA && isObjectB) {
            var isArrayA = Array.isArray(a);
            var isArrayB = Array.isArray(b);
            if (isArrayA && isArrayB) {
                return a.length === b.length && a.every(function (e, i) {
                    return looseEqual(e, b[i])
                })
            } else if (a instanceof Date && b instanceof Date) {
                return a.getTime() === b.getTime()
            } else if (!isArrayA && !isArrayB) {
                var keysA = Object.keys(a);
                var keysB = Object.keys(b);
                return keysA.length === keysB.length && keysA.every(function (key) {
                    return looseEqual(a[key], b[key])
                })
            } else {
                return false
            }
        } else if (!isObjectA && !isObjectB) {
            return String(a) === String(b)
        } else {
            return false
        }
    }

    var createUid = (function () {
        var uid = 0;
        return function () {
            return uid++;
        }
    }());

    function create(baseObj, options) {
        var obj = Object.create(baseObj);
        return extend(obj, options);
    }

    function extend(obj, options) {
        options = options || {};
        for (var key in options) {
            var desptor = Object.getOwnPropertyDescriptor(options, key);
            if (typeof desptor.value !== "undefined") {
                obj[key] = desptor.value
            } else {
                if (desptor.get && desptor.set) {
                    Object.defineProperty(obj, key, {
                        get: desptor.get,
                        set: desptor.set
                    });
                } else if (desptor.get) {
                    Object.defineProperty(obj, key, {
                        get: desptor.get
                    });
                } else {
                    Object.defineProperty(obj, key, {
                        set: desptor.set
                    });
                }
            }
        }
        return obj;
    }

    function getEvent(list, obj) {
        for (var i = 0, len = list.length; i < len; i++) {
            var item = list[i];
            if (Object.keys(obj).every(function (key) {
                return looseEqual(obj[key], item[key]);
            })) return list[i];
        }
        return false;
    };

    function serialize(obj) {
        var parts = [];

        for (var key in obj) {
            addPart(obj, key, parts);
        }

        if (parts.length == 0) return null;
        return parts.join("&");

        function addPart(obj, key, parts, str) {
            str = str || key;
            if (isObject(obj[key])) {
                for (var k in obj[key]) {
                    addPart(obj[key], k, parts, key + "[" + k + "]");
                }
            } else if (Array.isArray(obj[key])) {
                for (var i = 0, len = obj[key].length; i < len; i++) {
                    if (isObject(obj[key][i])) {
                        for (var k in obj[key][i]) {
                            addPart(obj[key][i], k, parts, str + "[" + i + "]" + "[" + k + "]");
                        }
                    } else {
                        parts.push(encodeURIComponent(str) + "[]=" + encodeURIComponent(obj[key][i]));
                    }
                }
            } else {
                parts.push(encodeURIComponent(str) + "=" + encodeURIComponent(obj[key]));
            }
        }
    }

    function createRequest(target, url, data, feeback, option, method) {
        option = option || {};
        option.header = option.header || {};
        option.header["x-request-with"] = "XMLHttpRequest";
        if (!("Content-Type" in option.header)) option.header["Content-Type"] = "application/json";
        option.method = method || "GET";
        option.success = feeback.bind(target);
        if ("error" in option) option.error = option.error.bind(target);
        if ("onabort" in option) option.onabort = option.onabort.bind(target);
        option.target = target;
        option.url = url;
        option.data = data;
        option.async = typeof option.async === "undefined" ? true : option.async;
        return option;
    }

    var componentfilter = (function () {
        var preNode = null, pattern = /^([a-zA-Z]+-)+[a-zA-Z]+$/;
        return {
            destroy: function () {
                preNode = null;
            },
            filter: function (node) {
                if (node.constructor.name === "HTMLElement" && pattern.test(node.tagName)) {
                    if (preNode && preNode.contains(node)) return NodeFilter.FILTER_SKIP;
                    preNode = node;
                    return NodeFilter.FILTER_ACCEPT;
                }
                return NodeFilter.FILTER_SKIP;
            }
        }
    })();

    function Stopwatch() {
        this.reset();
    }
    Stopwatch.prototype = {
        constructor: Stopwatch,
        start: function () {
            this.startTime = Date.now();
            this.elapsed = undefined;
            this.running = true;
            this.stopDuration = 0;
            this.stopTime = 0;
        },
        stop: function () {
            if (this.running) {
                var now = Date.now();
                this.elapsed = now - this.stopDuration - this.startTime;
                this.running = false;
                this.stopTime = now;
            }
        },
        run: function () {
            if (!this.running && this.startTime != 0) {
                this.stopDuration += Date.now() - this.stopTime;
                this.running = true;
            }
        },
        getElapsedTime: function () {
            if (this.running) {
                return Date.now() - this.stopDuration - this.startTime;
            } else {
                return this.elapsed;
            }
        },
        reset: function () {
            this.startTime = 0;
            this.running = false;
            this.elapsed = undefined;
            this.stopDuration = 0;
            this.stopTime = 0;
        }
    };

    function EventDispatcher() {
        this.listeners = {};
    }
    EventDispatcher.prototype = {
        constructor: EventDispatcher,
        addEventListener: function (type, listener) {
            this._addEventListener(type, listener);
        },
        _addEventListener: function (type, listener, isOnce) {
            var listeners = this.listeners;
            if (typeof listeners[type] == "undefined") {
                listeners[type] = [];
            }
            if (isOnce) listener.once = true;
            listeners[type].push(listener);
        },
        addOnce: function (type, listener) {
            this._addEventListener(type, listener, true);
        },
        removeEventListener: function (type, listener) {
            var listeners = this.listeners;
            var handlers = listeners[type];
            if (Array.isArray(handlers)) {
                var i = handlers.indexOf(listener);
                handlers.splice(i, 1);
            }
        },
        clearListenerByType: function (type) {
            var handlers = this.listeners[type];
            if (Array.isArray(handlers)) handlers.length = 0;
        },
        dispatchEvent: function (event) {
            var listeners = this.listeners;
            if (!event.target) {
                event.target = this;
            }
            var handlers = listeners[event.type];
            if (Array.isArray(handlers)) {
                var onceIndexs = [];
                for (var i = 0, len = handlers.length; i < len; i++) {
                    if (handlers[i].once) {
                        onceIndexs.push(i);
                    }
                    handlers[i](event);
                }
                if (onceIndexs.length > 0) {
                    for (var i = onceIndexs.length - 1; i >= 0; i--) {
                        handlers.splice(onceIndexs[i], 1);
                    }
                }
            }
        },
        destroy: function () {
            for (var str in this.listeners) {
                this.listeners[str].length = 0;
                delete this.listeners[str];
            }
        }
    };

    function LoadUnit(version) {
        EventDispatcher.call(this);
        this.version = version;
        this.config = [];
        this.units = {};
        this.loadObj = {};
    }
    LoadUnit.prototype = create(EventDispatcher.prototype, {
        constructor: LoadUnit,
        loadConfigs: function (list, feeback) {
            this.config = list;
            if (list && list.length > 0) {
                var len = list.length;
                for (var i = 0; i < list.length; i++) {
                    this._getUnit(list[i], function () {
                        if (--len == 0) feeback();
                    })
                }
            }
            else feeback();
        },
        getUnit: function (name, next, beforeNext) {
            var config, isUrl = name.indexOf("/") > -1;
            if (isUrl) config = this._getConfigByUrl(name);
            else config = this._getConfigByName(name);
            if (!config) console.log("unit: " + name + ", can't get path");
            config = config || this._getConfigByUrl("404") || this._getConfigByName("500");
            if (typeof beforeNext === "function") beforeNext(config);
            this._getUnit(config, next, this.version, isUrl);
        },
        _getUnit: function (config, next, version, isUrl) {
            var that = this;
            if (config) {
                var name = config.name, url = config.js + (version ? "?v=" + version : "");
                if (this.units[name]) {
                    next(this.units[name], config);
                }
                else {
                    this.addOnce(name, function (obj) {
                        var opt = obj.detail.config || {};
                        if (isUrl) {
                            for (var key in opt) config[key] = config[key] || opt[key];
                        }
                        next(obj.detail.component, config);
                    });
                    if (!this.loadObj[name]) {
                        this.loadObj[name] = true;
                        var script = document.createElement("script"),
                            head = document.head;
                        script.dataset.name = name;
                        script.src = App.join(url);
                        script.onload = function () {
                            that.loadObj[name] = false;
                        }
                        script.onerror = function () {
                            console.warn(name + " can't onload; js url " + script.src);
                            that.loadObj[name] = false;
                        }
                        head.appendChild(script);
                    }
                }
            }
            else {
                throw new Error("未获得配置项");
            }
        },
        _getConfigByName: function (name) {
            var config = this.config;
            for (var i = 0, len = config.length; i < len; i++) {
                if (config[i].name === name) return config[i];
            }
            return false;
        },
        _getConfigByUrl: function (url) {
            var config = this.config;
            for (var i = 0, len = config.length; i < len; i++) {
                var obj = config[i];
                var result = App.configUrl(obj.url, url);
                if (result) {
                    for (var key in obj) {
                        result[key] = obj[key];
                    }
                    result.url = url;
                    return result;
                }
            }
            return false;
        },
        setConfig: function (config) {
            this.config = config;
        },
        appendCondig: function (config) {
            for (var i = 0, len = config.length; i < len; i++) {
                this.config.push(config[i]);
            }
        }
    });

    function BaseProto() {
        this.eventDispatcher = new EventDispatcher();
    }
    BaseProto.prototype = {
        constructor: BaseProto,
        _getFilter: function (key) {
            if ("filter" in this && key in this.filter) return {
                target: this, func: this.filter[key]
            };
            var parent = this.parent;
            if (parent) return this.parent._getFilter(key);
            return null;
        },
        attachDiyEvent: function (type, fn, isOnce, isBind) {
            if (!isBind) fn = fn.bind(this);
            if (isOnce) this.eventDispatcher.addOnce(type, fn);
            else this.eventDispatcher.addEventListener(type, fn);
            return this;
        },
        dispatchEvent: function (type, eventObj) {
            if (typeof type === "object") {
                this.eventDispatcher.dispatchEvent(type);
                return this;
            }
            eventObj = eventObj || {};
            eventObj.type = type;
            this.eventDispatcher.dispatchEvent(eventObj);
            return this;
        }
    }

    function Animation(time, delay, type) {
        BaseProto.call(this);
        this.time = time;
        this.animationType = type || Animation.linear;
        if (type == Animation.normal) this.time = Infinity;
        this.delay = delay || 0;
        this.stopwatch = new Stopwatch();
        this.ended = false;
    };
    Animation.prototype = create(BaseProto.prototype, {
        constructor: Animation,
        _start: function () {
            this.stopwatch.start();
            this.dispatchEvent("start");
        },
        _update: function (elapsed) {
            elapsed = elapsed || (this.stopwatch.getElapsedTime() - this.delay);
            if (elapsed > 0) {
                if (this.animationType == Animation.normal) {
                    if (this.ended) {
                        this._complete();
                        return true;
                    } else {
                        this.dispatchEvent("update", { elapsed: elapsed });
                        return false;
                    }
                } else {
                    if (this.ended || elapsed > this.time) {
                        this._complete();
                        return true;
                    } else {
                        var k = this.animationType(elapsed, this.time);
                        this.dispatchEvent("update", { elapsed: k });
                        return false;
                    }
                }
            }
            return false;
        },
        _complete: function () {
            this.dispatchEvent("complete");
            this.stopwatch.stop();
        },
        destroy: function () {
            this.eventDispatcher.destroy();
            this.stopwatch = null;
        },
        onUpdate: function (fn) {
            this.eventDispatcher.clearListenerByType("update");
            return this.attachDiyEvent("update", fn);
        },
        onComplete: function (fn) {
            this.eventDispatcher.clearListenerByType("complete");
            return this.attachDiyEvent("complete", fn);
        },
        onStart: function (fn) {
            return this.attachDiyEvent("start", fn);
        },
    });

    Animation.normal = function () {
        return Date.now();
    }
    Animation.cubicBezier = function (x1, y1, x2, y2) {
        function _getBezierValue(p1, p2, p3, p4, t) {
            var pp1 = (1 - t) * p1 + t * p2,
                pp2 = (1 - t) * p2 + t * p3,
                pp3 = (1 - t) * p3 + t * p4;
            var ppp1 = (1 - t) * pp1 + t * pp2,
                ppp2 = (1 - t) * pp2 + t * pp3;
            return (1 - t) * ppp1 + t * ppp2;
        }

        function _getElapsed(delta, p1, p2, p3, p4) {
            var half = _getBezierValue(p1, p2, p3, p4, .5);
            if (Math.abs(delta - half) < 0.0001) return half;
            if (delta > half) {
                return _getElapsed(delta, half, (p3 + p4 + p3 + p2) / 4, (p3 + p4) / 2, p4);
            } else {
                return _getElapsed(delta, p1, (p1 + p2) / 2, (p1 + p2 + p2 + p3) / 4, half);
            }
        }

        return function (elapsed, end) {
            if (elapsed == 0) return 0;
            if (elapsed == 1) return 1;
            var t = _getElapsed(elapsed / end, 0, x1, x2, 1);
            var dy0 = t * y2, dy1 = (1 - t) * y1 + y2 * t, dy2 = (1 - t) * y2 + t;
            var ddy1 = (1 - t) * dy1 + t * dy2,
                ddy0 = (1 - t) * dy0 + t * dy1;
            return ddy0 * (1 - t) + ddy1 * t;
        }
    }
    Animation.ease = Animation.cubicBezier(0.25, 0.1, 0.25, 1);
    Animation.linear = Animation.cubicBezier(0, 0, 1, 1);
    Animation.easeIn = Animation.cubicBezier(0, 0, 0.58, 1);
    Animation.easeInOut = Animation.cubicBezier(0.42, 0, 0.58, 1);

    Animation.steps = function (nums, isStart) {
        return function (elapsed, end) {
            var t = elapsed / end;
            var perElapsed = 1 / nums, n = Math.round(t / perElapsed);
            if (isStart == 0) return n / nums == 1 ? (nums - 1) / num : n / nums;
            return (n + 1) / nums > 1 ? 1 : (n + 1) / nums;
        }
    };
    Animation.stepStart = function (nums) {
        return Animation.steps(nums, 0);
    }
    Animation.stepEnd = function (nums) {
        return Animation.steps(nums, 1);
    }

    function AnimationList() {
        this.reset();
    }
    AnimationList.prototype = {
        constructor: AnimationList,
        add: function (animation) {
            if (!this._contains(animation)) {
                animation.stopwatch.reset();
                animation._start();
                this.animationArray.push(animation);
                if (!this.animationObj[animation.target]) {
                    this.animationObj[animation.target] = [animation];
                } else {
                    this.animationObj[animation.target].push(animation);
                }

                if (!this.running) {
                    this.running = true;
                    this.requestId = requestAnimationFrame(this.tick.bind(this));
                }
            }
        },
        _contains: function (animation) {
            var animationArray = this.animationArray;
            for (var i = 0, len = animationArray.length; i < len; i++) {
                var aniObj = animationArray[i];
                if (animation === aniObj) {
                    return true;
                }
            }
            return false;
        },
        run: function () {
            if (this.running) return;
            var animationArray = this.animationArray;
            for (var i = 0, len = animationArray.length; i < len; i++) {
                animationArray[i].stopwatch.run();
            }
            this.running = true;

            this.requestId = requestAnimationFrame(this.tick.bind(this));
        },
        tick: function () {
            var animationArray = this.animationArray;
            var len = animationArray.length;
            if (len == 0) {
                this.requestId = null;
                this.running = false;
                return;
            }

            var removes = [];
            for (var i = 0; i < len; i++) {
                var aniObj = animationArray[i];

                if (!aniObj.stopwatch.running) continue;
                if (aniObj._update()) {
                    removes.push(aniObj);
                }
            }

            for (var i = animationArray.length - 1; i >= 0; i--) {
                var a = animationArray[i];
                if (removes.indexOf(a) > -1) {
                    this._remove(a);
                }
            }
            if (animationArray.length > 0) this.requestId = requestAnimationFrame(this.tick.bind(this));
        },
        _removeByTarget: function (str) {
            var animationArray = this.animationArray,
                animationObj = this.animationObj,
                array = animationObj[str];

            if (array) {
                for (var i = 0; i < array.length; i++) {
                    var index = animationArray.indexOf(array[i]);
                    animationArray.splice(index, 1);
                }
                delete animationObj[str];
            }

            if (animationArray.length == 0) this.stop(true);
        },
        _remove: function (animation) {
            var animationArray = this.animationArray,
                animationObj = this.animationObj,
                array = animationObj[animation.target];

            var i = animationArray.indexOf(animation);
            animationArray.splice(i, 1);
            var ii = array.indexOf(animation);
            array.splice(ii, 1);
            if (array.length == 0) {
                delete animationObj[animation.target];
            }

            if (animationArray.length == 0) this.stop(true);
        },
        stop: function (isOver) {
            cancelAnimationFrame(this.requestId);
            this.running = false;
            if (isOver) return;
            var animationArray = this.animationArray;
            for (var i = 0, len = animationArray.length; i < len; i++) {
                animationArray[i].stopwatch.stop();
            }
        },
        reset: function () {
            this.animationArray = [];
            this.animationObj = {};
            this.running = false;
            this.requestId = null;
        },
        destroy: function () {
            this.stop();
            this.reset();
        }
    };

    function HtmlProto() {
        BaseProto.call(this);
        this.domList = {};
        this.eventList = [];
        this.data = {};
        this.uid = createUid();

        this.components = [];
        this.template = document.createElement("template");
        this.parent = null;

        this.http = new Http(this);
        this.nodes = [];
        this.isSave = false;

        this.parentDom = null;
    }
    HtmlProto.prototype = create(BaseProto.prototype, {
        constructor: HtmlProto,
        dispatchEventThroughTree: function (name) {
            this.dispatchEvent(name);
            for (var i = 0; i < this.components.length; i++) {
                this.components[i].dispatchEventThroughTree(name);
            }
        },
        abortXhr: function (xhr) {
            return this.http.remove(xhr);
        },
        fetch: function (url, bk) {
            var url = this.computeUrl(url);
            var res = Http.cache.get(url);
            if (res) return bk.call(this, res);
            else {
                if (Http.cache.set(url, bk, this)) {
                    if (typeof __dirname === "string") {
                        require("fs").readFile(url, "utf-8", function (error, result) {
                            if (error) console.log(error);
                            else Http.cache.dispatch(url, result);
                        })
                    }
                    else {
                        var obj = createRequest(this, url, undefined, function (result) {
                            Http.cache.dispatch(url, result);
                        }, {
                            onabort: function (ev) {
                                Http.cache.remove(url);
                            }
                        });
                        this.http.ajax(obj);
                    }
                }
            }
        },
        computeUrl: function (url) {
            var baseUrl = this.baseUrl;
            if (url.indexOf("/") === 0) return url + "?v=" + App.version;
            if (url.indexOf("./") === 0) return baseUrl + url.slice(2) + "?v=" + App.version;
            while (url.indexOf("../") === 0) {
                baseUrl = baseUrl.split("/").slice(0, -2).join("/") + "/";
                url = url.slice(3);
            }
            return baseUrl + url + "?v=" + App.version;
        },
        fetchAll: function (urllist, bk) {
            var result = [],
                len = urllist.length,
                that = this;

            var resBack = function (index) {
                return function (res) {
                    result[index] = res;
                    if (--len === 0) bk.call(that, result);
                }
            };
            urllist.forEach(function (url, index) {
                that.fetch(url, resBack(index));
            });
        },
        attachEvent: function (key, eventStr, fn, passive, doFn) {
            passive = passive || false;
            var eventList = this.eventList;
            if (typeof fn !== "function" && typeof doFn !== "function") return this;
            doFn = doFn || fn.bind(this);
            var eventObj = getEvent(eventList, {
                key: key
            });
            if (eventObj) {
                var eventArray = eventObj.eventArray;
                var methodEventObj = getEvent(eventArray, {
                    method: eventStr
                });
                if (methodEventObj) {
                    var fnArray = methodEventObj.fnArray;
                    var obj = getEvent(fnArray, {
                        backFn: fn,
                        passive: passive
                    })
                    if (!obj) {
                        fnArray.push({
                            backFn: fn,
                            passive: passive,
                            doFn: doFn
                        });
                    }
                } else {
                    eventArray.push({
                        method: eventStr,
                        fnArray: [{
                            backFn: fn,
                            passive: passive,
                            doFn: doFn
                        }]
                    })
                }
            } else {
                eventList.push({
                    key: key,
                    eventArray: [{
                        method: eventStr,
                        fnArray: [{
                            backFn: fn,
                            passive: passive,
                            doFn: doFn
                        }]
                    }]
                })
            }
            return this;
        },
        attachDom: function (cssQuery, key) {
            var dom = null;
            if (cssQuery.indexOf("#") == 0) {
                var id = cssQuery.slice(1);
                dom = this.getComponentById(id, true);
                if (dom) {
                    this.domList[key] = dom;
                    return this;
                }
            }
            this.domList[key] = this.template.content.querySelector(cssQuery);
            return this;
        },
        getComponentById: function (id, isExcept) {
            if (!isExcept && this.id === id) return this;
            var components = this.components;
            for (var i = 0; i < components.length; i++) {
                var com = components[i];
                var idCom = com.getComponentById(id);
                if (idCom) return idCom;
            }
            return null;
        },
        deleteRange: function (range) {
            var components = this.components, removeComponents = [];
            for (var i = 0; i < components.length; i++) {
                var node = components[i].nodes[0];
                if (!document.body.contains(node)) continue;
                var compareRange = document.createRange();
                compareRange.selectNode(node);

                if (range.compareBoundaryPoints(Range.END_TO_START, compareRange) > -1) {
                    compareRange.detach();
                    continue;
                }
                if (range.compareBoundaryPoints(Range.START_TO_END, compareRange) < 1) {
                    compareRange.detach();
                    break;
                }

                removeComponents.push(components[i]);
            }

            for (var i = 0; i < removeComponents.length; i++) {
                this.removeComponent(removeComponents[i]);
            }

            range.deleteContents();
        },
        _beforeInit: function (next) {
            var that = this;
            if (typeof this.beforeInit === "function") {
                this.beforeInit(function () {
                    that._init.apply(that, arguments);
                    if (typeof next === "function") next();
                })
            }
            else {
                this._init();
                if (typeof next === "function") next();
            }
        },
        attachDomEvent: function (cssQuery, key, eventStr, handler, passive, doFn) {
            return this.attachDom(cssQuery, key).attachEvent(key, eventStr, handler, passive, doFn);
        },
        renderHTML: (function () {
            var EACH_START = /{{each/g,
                EACH_END = /{{\/each}}/g,
                IF_START = /{{if/g,
                IF_END = /{{\/if}}/g,
                ELSEIF = /{{elseif/g,
                ELSE = /{{\s*else\s*}}/g;

            function render(str, obj, isEach, isIf, target) {
                var that = target || this;
                obj = obj || (that.constructor === Page ? App.extend({}, that.data) : App.extend(App.extend({}, that.data), that.property));

                if (!isEach) str = filter(str, obj, EACH_START, EACH_END, renderEachType, 9, that);
                if (!isIf) str = filter(str, obj, IF_START, IF_END, renderIfType, 7, that);

                var pattern = /{?{{\s*[a-zA-Z_$\[\]][\w\d$\[\]]*(\.[a-zA-Z_$\[\]][\w\d$\[\]]*)*\s*(\|\s*[a-zA-Z_$\[\]][\w$\[\]]*)*(:(\S*)*)?\s*}}}?/g;
                var newStr = str.replace(pattern, function (match) {
                    if (ELSE.test(match)) return match;
                    var isdecode = match.indexOf("{{{") > -1;
                    var len = isdecode ? 3 : 2;
                    return getProperty(obj, match.slice(len, -len).replace(/^\s+|\s+$/g, ""), that, !isdecode);
                });

                return newStr;
            }

            function getIndexList(str, pattern) {
                var list = [],
                    matches = pattern.exec(str);
                while (matches) {
                    list.push(matches.index);
                    matches = pattern.exec(str);
                }
                return list;
            }

            function getConditionStr(str, start, end, slen) {
                var result = [],
                    s, e, strArray = [],
                    len = 0,
                    bIndex, eIndex,
                    startArray = getIndexList(str, start),
                    endArray = getIndexList(str, end);

                if (startArray.length !== endArray.length) throw new Error("没有对应语句，请查看模板代码是否错误");
                while (startArray.length > 0 || endArray.length > 0) {
                    s = typeof startArray[0] === "undefined" ? Infinity : startArray[0];
                    e = endArray[0] ? endArray[0] : 0;
                    if (s > e) {
                        result.push({
                            type: 0,
                            index: endArray[0] + slen
                        });
                        endArray.splice(0, 1);
                    } else {
                        result.push({
                            type: 1,
                            index: startArray[0]
                        });
                        startArray.splice(0, 1);
                    }
                }

                for (var i = 0; i < result.length; i++) {
                    var r = result[i];
                    if (r.type === 1) {
                        len++;
                        if (len === 1) bIndex = r.index;
                    } else {
                        len--;
                        if (len === 0) {
                            eIndex = r.index;
                            strArray.push({
                                str: str.slice(bIndex, eIndex),
                                start: bIndex,
                                end: eIndex
                            });
                        }
                    }
                }
                return strArray;
            }

            function splitConditionArray(str, list) {
                if (list.length == 0) return [{
                    type: 0,
                    str: str
                }];
                var result = [];
                if (list[0].start > 0) {
                    result.push({
                        type: 0,
                        str: str.slice(0, list[0].start)
                    });
                }
                var len = list.length;
                for (var i = 0; i < len; i++) {
                    result.push({
                        type: 1,
                        str: str.slice(list[i].start, list[i].end)
                    });
                    if (list[i + 1] && list[i + 1].start > list[i].end) {
                        result.push({
                            type: 0,
                            str: str.slice(list[i].end, list[i + 1].start)
                        });
                    }
                }
                if (str.length > list[len - 1].end) result.push({
                    type: 0,
                    str: str.slice(list[len - 1].end, str.length)
                });

                return result;
            }

            function filter(str, obj, startStr, endStr, iterater, len, target) {
                var result = getConditionStr(str, startStr, endStr, len);
                var list = splitConditionArray(str, result);
                var resultStr = list.map(function (strObj) {
                    return iterater(strObj, obj, target);
                }).join("");

                return resultStr;
            }

            function renderEachType(strObj, obj, target) {
                if (strObj.type === 0) return strObj.str;
                else if (strObj.type === 1) {
                    var str = "";
                    var pattern = /^{{each\s+([^}]*?)}}([\s\S]*){{\/each}}$/;
                    var testArray = pattern.exec(strObj.str);
                    if (testArray) {
                        var sub = testArray[2];
                        if (testArray[1].indexOf("as") === -1) {
                            var left = getProperty(obj, testArray[1], target);
                            if (left && Array.isArray(left)) {
                                for (var i = 0; i < left.length; i++) {
                                    var subObj = JSON.parse(JSON.stringify(obj)), tempObj = left[i];
                                    if (typeof tempObj !== "object") tempObj = { content: tempObj };
                                    for (var key in tempObj) {
                                        subObj[key] = tempObj[key];
                                    }
                                    if (subObj.index) subObj.$index = subObj.index;
                                    subObj.index = i + 1;
                                    str += render(sub, subObj, false, false, target);
                                }
                            }
                        }
                        else {
                            var testPattern = /(\S+)\s+as\s+(\S+)/;
                            var tArray = testPattern.exec(testArray[1]);
                            var left = getProperty(obj, tArray[2], target), name = tArray[1];

                            if (left && Array.isArray(left)) {
                                for (var i = 0; i < left.length; i++) {
                                    var subObj = JSON.parse(JSON.stringify(obj)), tempObj = left[i];
                                    if (typeof tempObj !== "object") tempObj = { content: tempObj };
                                    var tObj = subObj[name] = {};
                                    for (var key in tempObj) {
                                        tObj[key] = tempObj[key];
                                    }
                                    if (tObj.index) tObj.$index = tObj.index;
                                    tObj.index = i + 1;
                                    str += render(sub, subObj, false, false, target);
                                }
                            }
                        }
                    }
                    return str;
                }
            }

            function getTestArray(str) {
                var ifArray = getIndexList(str, IF_START),
                    elseifArray = getIndexList(str, ELSEIF),
                    elseArray = getIndexList(str, ELSE),
                    endArray = getIndexList(str, IF_END);

                var result = [{
                    type: 0,
                    index: ifArray[0]
                }],
                    i, index;
                for (i = 0; i < elseifArray.length; i++) {
                    index = elseifArray[i];
                    var ifLen = 0,
                        j;
                    for (j = 0; j < ifArray.length; j++) {
                        if (index > ifArray[j]) ifLen++;
                        else break;
                    }
                    for (j = 0; j < endArray.length; j++) {
                        if (index > endArray[j]) ifLen--;
                        else break;
                    }

                    if (ifLen === 1) result.push({
                        type: 1,
                        index: index
                    });
                }

                for (i = 0; i < elseArray.length; i++) {
                    index = elseArray[i];
                    var elseLen = 0,
                        j;
                    for (j = 0; j < ifArray.length; j++) {
                        if (index > ifArray[j]) elseLen++;
                        else break;
                    }
                    for (j = 0; j < endArray.length; j++) {
                        if (index > endArray[j]) elseLen--;
                        else break;
                    }

                    if (elseLen === 1) result.push({
                        type: 2,
                        index: index
                    });
                }

                result = result.sort(function (prev, next) {
                    return prev.index - next.index;
                });

                var resultObj = {
                    conditions: [],
                    addition: {
                        content: ""
                    }
                };

                for (var i = 0; i < result.length; i++) {
                    var contentStr;
                    var current = result[i];
                    if (i < result.length - 1) contentStr = str.slice(result[i].index, result[i + 1].index);
                    else contentStr = str.slice(result[i].index);
                    if (current.type !== 2) {
                        var pattern = /{{(?:elseif|if)\s+([^}]*)\s*}}/;
                        var testArray = pattern.exec(contentStr);
                        if (testArray) {
                            var startIndex = contentStr.indexOf(testArray[0]) + testArray[0].length;
                            var endIndex = contentStr.lastIndexOf("{{/if}}");
                            if (endIndex + "{{/if}}".length !== contentStr.length
                                || (result[i + 1] && result[i + 1].type === 2)) endIndex = -1;
                            var content = endIndex === -1 ? contentStr.slice(startIndex) : contentStr.slice(startIndex, endIndex);
                            resultObj.conditions.push({
                                str: testArray[1],
                                content: content
                            });
                        }
                    } else {
                        var pattern = /{{else}}([\s\S]*?){{\/if}}$/;
                        var testArray = pattern.exec(contentStr);
                        if (testArray) {
                            resultObj.addition.content = testArray[1];
                        }
                    }
                }

                return resultObj;
            }

            function renderIfType(strObj, obj, target) {
                if (strObj.type === 0) return render(strObj.str, obj, true, true, target);
                else if (strObj.type === 1) {
                    var testObj = getTestArray(strObj.str);
                    for (var i = 0; i < testObj.conditions.length; i++) {
                        if (compute(obj, testObj.conditions[i].str, target)) {
                            return render(testObj.conditions[i].content, obj, true, false, target);
                        }
                    }
                    return render(testObj.addition.content, obj, true, false, target);
                }
            }

            function compute(obj, str, target) {
                var testExp = /([\s\S]*?)\s+(!=|==|>=|<=|>|<)\s+([\s\S]*)/;
                var testArray = testExp.exec(str);
                if (testArray) {
                    var left = testArray[1],
                        operation = testArray[2],
                        right = testArray[3];
                    if (right.indexOf("[") > -1) {
                        right = getProperty(obj, right.slice(1, -1), target);
                    }
                    var computeLeft = getProperty(obj, left, target);
                    if (operation && right) {
                        return compare[operation](computeLeft, right);
                    }
                } else {
                    if (str.indexOf("!") === 0) return !getProperty(obj, str.slice(1), target);
                    return getProperty(obj, str, target)
                }
            }

            function getProperty(obj, keyStr, target, isCode) {
                if (ELSE.test(keyStr)) return keyStr;
                keyStr = keyStr.replace(/\[(.*?)\]/g, function (item, match) {
                    return getProperty(obj, match, target, isCode);
                })
                var strArray = keyStr.split("|").map(function (item) {
                    return item.replace(/\s+/g, "");
                })
                var tempArray = strArray[0].split('.');
                var property = obj[tempArray[0]],
                    i = 1,
                    len = tempArray.length;

                while (len - i >= 1) {
                    property = property[tempArray[i]];
                    i++;
                }
                var filterStr = strArray[1];
                if (filterStr) {
                    var filterArray = filterStr.split(":"),
                        key = filterArray[0], args = filterArray[1] ? filterArray[1].split(",").map(function (item) {
                            return item.replace(/^\s|\s$/g, "");
                        }) : [];
                    args.unshift(property);
                    var filter = target._getFilter(key);
                    if (filter) property = filter.func.apply(filter.target, args);
                }

                if (isCode) {
                    property += "";
                    property = property.replace(/[<>"&]/g, function (match) {
                        return {
                            "<": "&lt;",
                            ">": "&gt;",
                            '"': "&quot;",
                            "&": "&amp;"
                        }[match];
                    });
                }
                return property;
            }

            var compare = {
                "==": function (left, right) {
                    return left == right;
                },
                "!=": function (left, right) {
                    return left != right;
                },
                ">=": function (left, right) {
                    return left >= right;
                },
                "<=": function (left, right) {
                    return left <= right;
                },
                "<": function (left, right) {
                    return left < right;
                },
                ">": function (left, right) {
                    return left > right;
                }
            };

            return render;
        })(),
        getDomObj: function () {
            throw new Error("getDomObj方法必须重写");
        },
        render: function (next) {
            throw new Error("render方法必须重写");
        },
        _beforeInitComponent: function (dom) {
            var components = [];
            var tree = document.createTreeWalker(dom, NodeFilter.SHOW_ELEMENT, componentfilter.filter, false);

            var component = tree.firstChild();
            while (component) {
                var obj = {
                    component: component,
                    id: component.id,
                    name: component.tagName.toLowerCase(),
                    slot: component.innerHTML,
                    dataset: component.dataset,
                    property: {}
                };
                var attributes = component.attributes;
                for (var i = 0; i < attributes.length; i++) {
                    var attribute = attributes[i];
                    obj.property[attribute.name] = attribute.value;
                }
                components.push(obj);
                component = tree.nextNode();
            }

            componentfilter.destroy();

            for (var i = 0; i < components.length; i++) {
                var component = components[i];
                var div = document.createElement("div");
                div.id = component.id;
                component.div = div;
                component.component.parentNode.replaceChild(div, component.component);
            }
            return components
        },
        beforeInitialize: function () { },
        _sortComponents: function () {
            this.components.sort(function (prev, next) {
                if (prev.nodes[0] && next.nodes[0]) {
                    var pos = prev.nodes[0].compareDocumentPosition(next.nodes[0]);
                    if (pos & 2) return 1;
                    return -1;
                }
                return 1;
            })
        },
        initialize: function (dom, html, option, feeback) {
            option = option || {};
            this.parentDom = dom;
            this.template.innerHTML = html;
            var fragment = this.template.content;
            var components = this._beforeInitComponent(fragment, html);

            var that = this;
            if (typeof this.setEnv === "function") this.setEnv();
            this._initComponent(components, function () {
                that.beforeInitialize(fragment);
                that.getDomObj();
                if (option.method === "insert") dom.insertBefore(fragment, dom.firstChild);
                else if (option.method === "replace") dom.parentNode.replaceChild(fragment, dom);
                else if (option.method === "before") dom.parentNode.insertBefore(fragment, dom);
                else {
                    dom.innerHTML = "";
                    dom.appendChild(fragment);
                }

                that._beforeInit(feeback);
            });
        },
        _setDispose: function () {
            this.eventDispatcher.clearListenerByType("_dispose");
            this.attachDiyEvent("_dispose", function () {
                if (typeof this.dispose === "function") this.dispose();
                var app = this._getApp();
                if (app) app.removeAnimationByTarget(this);
                this._removeEventListeners();

                for (var i = this.components.length - 1; i >= 0; i--) {
                    this.components[i].destroy();
                }

                this.http.destroy();
            }, true)
        },
        _initComponent: function (components, next) {
            if (components.length === 0) return next();

            var len = components.length;
            var feeback = function () {
                if (--len === 0) {
                    next();
                }
            }

            for (var i = 0; i < components.length; i++) {
                this._addComponent(components[i], feeback);
            }
        },
        _init: function () {
            this.isSave = false;
            this._addEventListeners();
            if (typeof this.init === "function") this.init.apply(this, arguments);
        },
        save: function () {
            this.isSave = true;
            this.destroy(false);
        },
        destroy: function (isClean) {
            this.dispatchEvent("_dispose");

            if (isClean) {
                if (typeof this.beforeDestroy === "function") this.beforeDestroy();
                this.eventDispatcher.destroy();
                this.eventList.length = 0;
                this._removeDom();

                for (var i = this.components.length - 1; i >= 0; i--) {
                    this.components[i].destroy(isClean);
                    if (isClean) this.components.splice(i, 1);
                }

                this.template = null;
                this.nodes.length = 0;
                this.parent = null;
                this.data = {};
                this.parentDom = null;
            }
        },
        _removeEventListeners: function () {
            var domList = this.domList,
                eventList = this.eventList;

            for (var i = 0, len = eventList.length; i < len; i++) {
                var eventObj = eventList[i];
                var dom = domList[eventObj.key];
                if (!dom) {
                    console.warn("dom:" + eventObj.key + ", 不存在");
                    continue;
                }
                if (dom.constructor === Component) {
                    dom = this.domList["_" + eventObj.key];
                }
                var eventArray = eventObj.eventArray;
                for (var j = 0, length = eventArray.length; j < length; j++) {
                    var methodEventObj = eventArray[j];
                    var key = methodEventObj.method;
                    var fnArray = methodEventObj.fnArray;
                    for (var ii = 0; ii < fnArray.length; ii++) {
                        if (dom) dom.removeEventListener(key, fnArray[ii].doFn, fnArray[ii].passive);
                    }
                }
            }
        },
        _addEventListeners: function () {
            var domList = this.domList,
                eventList = this.eventList;

            for (var i = 0, len = eventList.length; i < len; i++) {
                var eventObj = eventList[i];
                var dom = domList[eventObj.key];
                if (!dom) {
                    console.warn("dom:" + eventObj.key + ", 不存在");
                    continue;
                }
                if (dom.constructor === Component) {
                    dom = dom.nodes[0];
                    this.domList["_" + eventObj.key] = dom;
                }

                var eventArray = eventObj.eventArray;
                for (var j = 0, length = eventArray.length; j < length; j++) {
                    var methodEventObj = eventArray[j];
                    var key = methodEventObj.method;
                    var fnArray = methodEventObj.fnArray;
                    for (var ii = 0; ii < fnArray.length; ii++) {
                        if (dom) dom.addEventListener(key, fnArray[ii].doFn, fnArray[ii].passive);
                    }
                }
            }
        },
        addDomEventListener: function (key, dom, eventStr, fn, passive) {
            if (this.domList[key]) console.warn("这个" + key + "已被使用，可能导致错误");
            this.domList[key] = dom;
            return this.addEventListener(key, eventStr, fn, passive);
        },
        addEventListener: function (key, eventStr, fn, passive) {
            var dom = this.domList[key];
            var doFn = fn.bind(this);
            this.attachEvent(key, eventStr, fn, passive, doFn);
            if (dom.constructor === Component) {
                dom = dom.nodes[0];
                this.domList["_" + key] = dom;
            }
            dom.addEventListener(eventStr, doFn, passive);

            return this;
        },
        _removeDom: function () {
            var domList = this.domList;

            for (var key in domList) {
                domList[key] = null;
            }
        },
        addAnimation: function (animation) {
            animation.target = this.uid;
            this._getApp().addAnimation(animation);
        },
        _getApp: function () {
            var parent = this;
            while (parent && parent.constructor !== App) {
                parent = parent.parent;
            }
            return parent;
        },
        _getPage: function () {
            var parent = this;
            while (parent.constructor !== Page) {
                parent = parent.parent;
            }
            return parent;
        },
        _getCurrentPage: function () {
            var app = this._getApp();
            return app.currentPage;
        },
        removeComponent: function (component) {
            var components = this.components;
            component.dispatchCustomEvent("removecomponent", {});
            for (var i = 0; i < components.length; i++) {
                if (components[i] === component) {
                    components.splice(i, 1);
                }
            }

            for (var i = 0; i < component.nodes.length; i++) {
                var node = component.nodes[i];
                node.parentNode.removeChild(node);
            }
            component.destroy(true);
        },
        _addComponent: function (component, feeback) {
            var that = this;
            var app = this._getApp();
            app.getComponentByName(component.name, function (com, config) {
                if (com) {
                    var newComponent = new com(), id = component.id;
                    newComponent.baseUrl = getBaseUrl(config.js);
                    if (id) newComponent.id = id;
                    newComponent.parent = that;
                    extend(newComponent.data, component.dataset);
                    extend(newComponent.dataset, component.dataset);
                    extend(newComponent.property, component.property);
                    newComponent.slot = component.slot;
                    newComponent.render(function (html) {
                        var nextHtml = newComponent.initSlot(html, newComponent.slot);
                        newComponent.initialize(component.div, nextHtml, {
                            method: "replace"
                        }, function () {
                            that.components.push(newComponent);
                            var node = newComponent.nodes[0];
                            if (node) {
                                for (var key in component.dataset) {
                                    node.dataset[key] = component.dataset[key];
                                }
                            }
                            if (typeof feeback === "function") feeback();
                        });
                    })
                } else {
                    if (typeof feeback === "function") feeback();
                }
            })
        },
        getComponentsByTagName: function (name) {
            var components = this.components,
                result = [];
            name = Array.isArray(name) ? name : [name];
            for (var i = 0; i < components.length; i++) {
                if (name.indexOf(components[i].tagName) - 1) {
                    result.push(components[i]);
                }
            }
            return result;
        },
        innerHTML: function (dom, html, feeback, obj) {
            this._removeComponentInDom(dom);

            dom.innerHTML = "";
            this._addComponentByHtml(dom, html, feeback, "beforeend", obj);
        },
        outerHTML: function (dom, html, feeback, obj) {
            this._removeComponentInDom(dom);

            this._addComponentByHtml(dom, html, feeback, "beforebegin", obj);
            dom.parentNode.removeChild(dom);
        },
        _removeComponentInDom: function (dom) {
            var components = this.components;
            for (var i = components.length - 1; i >= 0; i--) {
                var component = components[i],
                    node = component.nodes[0];
                if (node && dom.contains(component.nodes[0])) this.removeComponent(component);
            }
        },
        getComponentsByInDom: function (dom) {
            var components = this.components, result = [];
            for (var i = 0; i < components.length; i++) {
                var component = components[i],
                    node = component.nodes[0];
                if (node && dom.contains(component.nodes[0])) result.push(component);
            }
            return result;
        },
        insertAdjacentHTML: function (dom, html, pos, feeback, obj) {
            this._addComponentByHtml(dom, html, feeback, pos, obj);
        },
        _addComponentByHtml: function (dom, html, feeback, type, obj) {
            type = type.toLowerCase(), renderData = this.data;
            if (obj) {
                renderData = JSON.parse(JSON.stringify(this.data));
                for (key in obj) renderData[key] = obj[key];
            }

            this.template.innerHTML = this.renderHTML(html, renderData);
            var fragment = this.template.content, that = this;
            var components = this._beforeInitComponent(fragment);
            var component;
            switch (type) {
                case "beforebegin":
                    dom.parentNode.insertBefore(fragment, dom);
                    component = this._getComponentByDom(dom.parentNode);
                    break;
                case "afterbegin":
                    dom.insertBefore(fragment, dom.firstChild);
                    component = this._getComponentByDom(dom);
                    break;
                case "beforeend":
                    dom.appendChild(fragment);
                    component = this._getComponentByDom(dom);
                    break;
                case "afterend":
                    dom.parentNode.insertBefore(fragment, dom.nextElementSibling);
                    component = this._getComponentByDom(dom.parentNode);
                    break;
            }

            component._initComponent(components, function () {
                that._sortComponents();
                if (typeof feeback === "function") feeback.call(this);
            });
        },
        _getComponentByDom: function (dom) {
            var component = this, components = this.components;
            for (var i = 0; i < components.length; i++) {
                var nodes = components[i].nodes;
                for (var j = 0; j < nodes.length; j++) {
                    if (nodes[j].contains(dom)) return components[i]._getComponentByDom(dom);
                }
            }
            return component;
        },
    });

    function Slot(type, name, content, isOrigin) {
        this.name = name;
        this.content = content;
        this.type = type;
        this.isOrigin = !!isOrigin;
    }
    Slot.prototype = {
        constructor: Slot,
        get innerHTML() {
            if (this.type === "template") return "<template slot='" + this.name + "'>" + this.content + "</template>";
            return this.content;
        }
    }
    function Component(name) {
        HtmlProto.call(this);
        this.tagName = name;
        this.property = {};
        this.dataset = {};
        this.slots = [];
    }
    Component.prototype = create(HtmlProto.prototype, {
        constructor: Component,
        createPropertyStr: function (property, except) {
            except = except || [];
            property = property || this.property;
            var result = [];
            for (var key in property) {
                if (key == "id" || property[key] === "" || property[key] === false || typeof property[key] === "undefined" || except.indexOf(key) !== -1) continue;
                result.push(key + '="' + property[key] + '"');
            }
            return result.join(" ");
        },
        getCanCancelEvent: function () {
            return {
                isCancel: false,
                preventDefault: function () {
                    this.isCancel = true;
                }
            }
        },
        getCanTransDataEvent: function () {
            return {
                data: null,
                setData: function (data) {
                    this.data = data;
                },
                getData: function () {
                    var data = this.data;
                    this.data = null;
                    return data;
                }
            }
        },
        dispatchCustomEvent: function (name, data) {
            if (this.nodes.length > 0) {
                var event = document.createEvent("CustomEvent");
                event.initCustomEvent(name, true, true, data);
                this.nodes[0].dispatchEvent(event);
            }
            return this;
        },
        getSlotByName: function (name, type) {
            type = type || "template";
            for (var i = 0; i < this.slots.length; i++) {
                if (this.slots[i].name === name) {
                    return this.slots[i];
                }
            }
            var slot = new Slot(type, name, "", true);
            this.slots.push(slot);
            return slot;
        },
        _createTagStr: function () {
            var property = this.property;
            var strArray = ["<" + this.tagName];
            for (var key in property) {
                if (property[key]) strArray.push(key + '="' + property[key] + '"');
            }
            strArray.push(">");
            return strArray.join(" ");
        },
        getHTML: function () {
            var strArray = [];
            strArray.push(this._createTagStr());
            for (var i = 0; i < this.slots.length; i++) {
                var slot = this.slots[i];
                if (slot.content) strArray.push(slot.innerHTML);
            }
            strArray.push("</" + this.tagName + ">");
            return strArray.join("\n");
        },
        initSlot: function (html, slot) {
            var that = this;
            var slotPattern = /<slot[^>]*>[\s\S]*?<\/slot>/g;
            var slotContentPattern = /<slot[^>]*>([\s\S]*?)<\/slot>/;
            var hasNameSlotPattern = /^<slot[^>]*name=['"]([a-z_\-0-9]+)['"][^>]*>([\s\S]*?)<\/slot>$/;
            var nameSlotPattern = /<slot[^>]*name=['"]([a-z_\-0-9]+)['"][^>]*>[\s\S]*?<\/slot>/;
            if (nameSlotPattern.test(html)) {
                return html.replace(slotPattern, function (match) {
                    var matches = hasNameSlotPattern.exec(match);
                    if (matches) {
                        var pattern = new RegExp("<([a-z]+)[^>]*?slot=['\"]" + matches[1] + "['\"][^>]*>");
                        var mes = pattern.exec(slot);
                        if (!mes) return matches[2];
                        else {
                            var matchStr = mes[0], mslot,
                                name = mes[1];

                            var index = mes.index,
                                closeStr = "</" + name + ">",
                                startStr = "<" + name;

                            var closeIndex = slot.indexOf(closeStr, index + 1),
                                startIndex = slot.indexOf(startStr, index + 1);

                            if (closeIndex < startIndex) {
                                mslot = slot.slice(index, closeIndex + name.length + 3);
                            }
                            else {
                                while (closeIndex > startIndex && closeIndex !== -1 && startIndex !== -1) {
                                    startIndex = slot.indexOf(startStr, startIndex + 1);
                                    closeIndex = slot.indexOf(closeStr, closeIndex + 1);
                                }
                                mslot = slot.slice(index, closeIndex + name.length + 3);
                            }
                            slot = slot.replace(mslot, "");
                            if (mes[1] === "template") {
                                var tempPattern = new RegExp("^<template[^>]*?slot=['\"]" + matches[1] + "['\"][^>]*>([\\s\\S]*)<\\/template>$");
                                var tempMatch = tempPattern.exec(mslot.trim());
                                var tempslot = tempMatch[1];
                                that.slots.push(new Slot(mes[1], matches[1], tempslot));
                                return tempslot;
                            }
                            else {
                                that.slots.push(new Slot(mes[1], name, mslot));
                                return mslot;
                            }
                        }
                    } else {
                        var mes = slotContentPattern.exec(match);
                        var matchStr = mes ? mes[1] : match;
                        that.slots.push(new Slot("template", "default", matchStr));
                        return matchStr;
                    }
                });
            } else {
                return html.replace(slotPattern, function (match) {
                    var matches = hasNameSlotPattern.exec(match);
                    if (matches) {
                        that.slots.push(new Slot("template", "default", matches[2]));
                        return matches[2];
                    }
                    matches = slotContentPattern.exec(match);
                    var matchStr = "";
                    if (matches) matchStr = slot || matches[1];
                    else matchStr = slot;
                    that.slots.push(new Slot("template", "default", matchStr, matchStr === matches[1]));
                    return matchStr;
                });
            }
        },
        save: function () {
            HtmlProto.prototype.save.call(this);
            if (typeof this.beforeSave === "function") {
                this.beforeSave();
            }
            for (var i = 0; i < this.components.length; i++) {
                this.components[i].save();
            }
            if (this.parent.constructor == Page) {
                var div = document.createElement("div");
                div.id = this.uid;
                div.className = "loadinghidden";
                div.dataset.id = this.tagName;
                if (this.nodes.length > 0 && this.nodes[0].parentNode) {
                    this.nodes[0].parentNode.insertBefore(div, this.nodes[0]);
                }
            }

        },
        restore: function (option, str) {
            if (this.parent.constructor == Page) {
                var fragment = this.template.content;
                var div = document.getElementById(this.uid);
                for (var i = 0; i < this.nodes.length; i++) {
                    fragment.appendChild(this.nodes[i]);
                }
                if (div && div.parentNode) {
                    div.parentNode.replaceChild(fragment, div);
                }
            }

            for (var i = 0; i < this.components.length; i++) {
                this.components[i].restore(option, str);
            }

            if (typeof this.afterRestore === "function") {
                this.afterRestore();
            }
            this._setDispose();
            this._init();
        },
        beforeInitialize: function (fragment) {
            this.nodes.length = 0;
            this._setDispose();
            for (var i = 0; i < fragment.childNodes.length; i++) {
                var node = fragment.childNodes[i];
                if (node.nodeType === 1 || (node.nodeType === 3 && !(/^\s+$/.test(node.nodeValue)))) {
                    this.nodes.push(node);
                }
            }
            if (this.nodes.length == 0) console.log(this.nodes, this, fragment);
        }
    });

    Component.extend = function (name, option) {
        function ComponentExtend() {
            Component.call(this, name);
        }
        ComponentExtend.prototype = create(Component.prototype, option);

        return ComponentExtend;
    };
    Component.getNodeName = function (target, nodeName) {
        while (target.nodeName.toUpperCase() != nodeName) {
            target = target.parentNode;
            if (target == null) {
                return null;
            }
        }
        return target;
    }

    function Http(target) {
        this.target = target;
        this.list = [];
    }
    Http.prototype = {
        constructor: Http,
        ajax: function (option, bk) {
            var list = this.list, target = this.target,
                useType = false, commonHeader = Http.commonHeader;
            var xhr = new XMLHttpRequest();
            list.push(xhr);
            if (option.username)
                xhr.open(option.method, option.url, option.async, option.username, option.password);
            else
                xhr.open(option.method, option.url, option.async);
            var header = option.header || {};

            for (var key in commonHeader) {
                xhr.setRequestHeader(key, commonHeader[key])
            }
            for (var i in header) {
                if (header["Content-Type"] !== "multipart/form-data") {
                    xhr.setRequestHeader(i, header[i]);
                }
            }

            if ("type" in option) {
                xhr.responseType = option.type;
                useType = true;
            }

            xhr.onload = function () {
                target.dispatchEvent("xhrload", { xhr: xhr });
                if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
                    var index = list.indexOf(xhr);
                    list.splice(index, 1);
                    var result = useType ? xhr.response || xhr.responseText : xhr.responseText;
                    option.success.call(option.target, result);
                    if (typeof bk === "function") bk(result);
                } else {
                    if (typeof option.error === "function") option.error.call(target, xhr);
                }
                if (typeof option.complete === "function") option.complete.call(target, xhr);
                target.dispatchEvent("xhrcomplete", { xhr: xhr });
            };
            xhr.onerror = function () {
                target.dispatchEvent("xhrerror", { xhr: xhr });
                if (typeof option.error === "function") option.error.call(target, xhr);
                if (typeof option.complete === "function") option.complete.call(target, xhr);
                target.dispatchEvent("xhrcomplete", { xhr: xhr });
            };

            if ("onabort" in option) {
                xhr.onabort = option.onabort;
            }

            var data = null;
            if (option.data) {
                if (header["Content-Type"] == "application/json") data = JSON.stringify(option.data);
                else if (header["Content-Type"] == "multipart/form-data") {
                    data = new FormData();
                    for (var key in option.data) {
                        data.append(key, option.data[key]);
                    }
                }
                else data = serialize(option.data);
            }

            xhr.send(data);
            target.dispatchEvent("xhrstart", { xhr: xhr });
            return xhr;
        },
        destroy: function () {
            var list = this.list;
            for (var i = list.length - 1; i >= 0; i--) {
                list[i].abort();
                list.splice(i, 1);
            }
        },
        remove: function (xhr) {
            var index = this.indexOf(xhr);
            if (index > -1) {
                this.list.splice(index, 1);
                xhr.abort();
                return true;
            }
        },
    };

    Http.commonHeader = {};
    Http.cache = (function () {
        var cache = {};
        var progress = {};
        return {
            get: function (url) {
                return cache[url];
            },
            set: function (url, bk, target) {
                if (url in progress) {
                    progress[url].feebacks.push(bk);
                    progress[url].targets.push(target);
                    return false;
                } else {
                    progress[url] = {};
                    progress[url].feebacks = [bk];
                    progress[url].targets = [target];
                    return true;
                }
            },
            setInstance: function (url, str) {
                cache[url] = str;
            },
            remove: function (url) {
                delete progress[url];
            },
            dispatch: function (url, result) {
                if (url in progress) {
                    cache[url] = result;
                    for (var i = 0; i < progress[url].feebacks.length; i++) {
                        progress[url].feebacks[i].call(progress[url].targets[i], result);
                    }
                    delete progress[url];
                }
            }
        };
    })();

    function Page(name, title, url) {
        HtmlProto.call(this);
        this.title = title;
        this.url = url;
        this.name = name;
        this.popUp = null;
        this.isCompleteSave = false;
    }
    Page.prototype = create(HtmlProto.prototype, {
        constructor: Page,
        isInPopUp: function () {
            return this.parent.constructor === PopUp;
        },
        getHTML: function () {
            this._attachComponent();
            var html = this._replaceComponent(this.parentDom.innerHTML);
            this._detachComponent();
            return html;
        },
        _attachComponent: function () {
            var components = this.components;
            for (var i = 0; i < components.length; i++) {
                var com = components[i], nodes = com.nodes, uid = com.uid;
                for (var j = 0; j < nodes.length; j++) {
                    nodes[j].setAttribute("uid", uid + "_" + j);
                }
            }
        },
        _replaceComponent: function (html) {
            var components = this.components;
            for (var i = 0; i < components.length; i++) {
                html = this._replaceHTMLByComponent(html, components[i]);
            }
            return html;
        },
        _replaceHTMLByComponent: function (html, component) {
            var nodes = component.nodes;
            for (var i = 0; i < nodes.length; i++) {
                html = this._replaceHTML(html, component.uid + "_" + i, i == 0 ? component.getHTML() : "");
            }
            return html;
        },
        _replaceHTML: function (html, targetStr, replaceStr) {
            replaceStr = replaceStr || "";
            var pattern = new RegExp("<([a-z]+)[^>]*?uid=['\"]" + targetStr + "['\"][^>]*>");
            var matches = pattern.exec(html);
            if (matches) {
                var index = matches.index + 1, nodeName = matches[1];
                var closeStr = "</" + nodeName + ">", startStr = "<" + nodeName;
                var closeIndex = html.indexOf(closeStr, index),
                    startIndex = html.indexOf(startStr, index);

                if (closeIndex < startIndex) {
                    html = html.slice(0, matches.index) + replaceStr + html.slice(closeIndex + nodeName.length + 3);
                }
                else {
                    while (closeIndex > startIndex && closeIndex !== -1 && startIndex !== -1) {
                        startIndex = html.indexOf(startStr, startIndex + 1);
                        closeIndex = html.indexOf(closeStr, closeIndex + 1);
                    }
                    html = html.slice(0, matches.index) + replaceStr + html.slice(closeIndex + nodeName.length + 3);
                }

            }
            return html;
        },
        _detachComponent: function () {
            var components = this.components;
            for (var i = 0; i < components.length; i++) {
                var com = components[i], nodes = com.nodes;
                for (var j = 0; j < nodes.length; j++) {
                    nodes[j].removeAttribute("uid");
                }
            }
        },

        post: function (url, data, feeback, option) {
            var obj = createRequest(this, url, data, feeback, option, "POST");
            return this.http.ajax(obj);
        },
        get: function (url, feeback, option) {
            var obj = createRequest(this, url, undefined, feeback, option, "GET");
            return this.http.ajax(obj);
        },
        ajaxAll: function (optionList, bk) {
            var result = [],
                len = optionList.length,
                that = this;
            var resBack = function (index) {
                return function (res) {
                    result[index] = res;
                    if (--len === 0) bk.call(that, result);
                };
            }
            optionList.forEach(function (option, index) {
                option.success = resBack(index);
                that.http.ajax(option);
            });
        },
        showPopUp: function (popupName, data, isDismisBeforeShow, bk) {
            data = data || {};
            if (this.isShowPop) {
                return false;
            }
            var app = this._getApp(), that = this;
            if (this.parent.currentPage !== this || app.isLock) {
                this.isShowPop = false;
                return false;
            }
            app.getPopUpByName(popupName, function (popup) {
                that.isShowPop = true;
                var popUp = new popup(data.resetConfig);
                that._showPopUp(app, popUp, data, isDismisBeforeShow, bk);
            });
            return true;
        },
        _showPopUp: function (app, popUp, data, isDismisBeforeShow, bk) {
            var config, that = this;
            popUp.data = data;
            config = data.config || {
                in: {},
                out: {}
            };
            this.isShowPop = false;

            if (this.popUp) {
                this.popUp.hidden(false, hiddenBack);
            } else {
                hiddenBack();
            }

            function hiddenBack() {
                that.popUp = popUp;
                if (popUp.show(app.changeArea || app.staticPage.domList.pageContainer, config.in, that, isDismisBeforeShow)) {
                    if (typeof bk === "function") bk(popUp);
                    popUp.hideBack = function (bk) {
                        app.removePopUpHistory(bk);
                        that.popUp.destroy();
                        that.popUp = null;
                    }
                }
            }
        },
        destroy: function (isClean) {
            HtmlProto.prototype.destroy.call(this, isClean);

            var popUp = this.popUp;
            if (popUp) {
                if (popUp.isShow) popUp.hidden();
                else popUp.destroy();
                this.popUp = null;
            }
            if (isClean) this.filter = {};
        },
        save: function (isNext) {
            HtmlProto.prototype.save.call(this);
            if (!this.parentDom) return;
            this.isCompleteSave = true;
            this.parent.saveData(this.data, isNext);
            if (typeof this.beforeSave === "function") {
                this.beforeSave();
            }
            for (var i = 0; i < this.components.length; i++) {
                this.components[i].save();
            }
            var parentDom = this.parentDom;
            this.nodes = [];
            for (var i = 0; i < parentDom.childNodes.length; i++) {
                this.nodes.push(parentDom.childNodes[i]);
            }
        },
        restore: function (dom, config) {
            config = config || {};
            this.parentDom = dom;
            dom.innerHTML = "";
            var fragment = this.template.content;
            for (var i = 0; i < this.nodes.length; i++) {
                fragment.appendChild(this.nodes[i]);
            }
            this.nodes.length = 0;
            dom.appendChild(fragment);
            for (var i = 0; i < this.components.length; i++) {
                this.components[i].restore();
            }

            if (typeof this.afterRestore === "function") {
                this.afterRestore();
            }

            this._init();
        }
    });

    Page.extend = function (name, title, url, option) {
        function PageExtend() {
            Page.call(this, name, title, url);
        }
        PageExtend.prototype = create(Page.prototype, option);

        return PageExtend;
    }

    function History(app) {
        this.app = app;
        this.appStorage = null;
        this.popUpStorages = [];
        this.popUps = [];
        this.popIndex = null;

        this.popBack = null;
        this.event = {
            popHandler: this._popHandler.bind(this)
        };
    }
    History.prototype = {
        constructor: History,
        forward: function (option) {
            var pagename = this.appStorage.components[this.appStorage.index + 1].name;
            this.app.render(pagename, false, option);
        },
        addPopUp: function (popUp) {
            this.popUpStorages.push(popUp.history);
            this.popUps.push(popUp);
            popUp.history.initialize(popUp.name);
            if (this.popIndex === null) this.popIndex = 0;
            else this.popIndex++;
        },
        backTo: function (num, option, bk) {
            var that = this;
            bk = bk || function () {
                that.forward(option);
            }
            this.skipPop = true;
            this.popBack = function () {
                bk(option);
                this.skipPop = false;
            }
            // 当前弹窗无法操作其它弹窗
            var info = this._getPopUpInfo(num);
            if (info) {
                info.target.index -= num + 1;
            }
            else {
                this.appStorage.index -= num + 1;

            }
            history.go(-num - 1);
        },
        _getPopUpInfo: function (num) {
            var popUpStorages = this.popUpStorages, len = popUpStorages.length;
            if (len == 0) return false;
            if (popUpStorages[len - 1].history.length < num) throw new Error("历史操作大于弹窗历史数");
            return {
                target: popUpStorages[len - 1]
            }
        },
        resetPopUps: function () {
            this.popIndex = null;
            this.popUpStorages.length = 0;
            this.popUps.length = 0;
        },
        removeCurrentPopup: function () {
            this.popUpStorages.length = this.popIndex;
            this.popUps.length = this.popIndex;
            this.popIndex--;
        },
        removePopUp: function (bk) {
            var that = this, popUpStorage = this.popUpStorages[this.popIndex]
            app = this.app;

            this.skipPop = true,

                this.popBack = function () {
                    if (that.popIndex === 0) {
                        that.resetPopUps();
                        var needObj = that.appStorage.getNeedData();
                        if (location.protocol !== "file:") {
                            for (var i = 0; i < needObj.history.length; i++) {
                                history.pushState(needObj.datas[i], "", needObj.history[i]);
                            }
                        }

                        that.skipPop = true;
                        that.popBack = function () {
                            that.popBack = null;
                            that.skipPop = false;
                            that.hidenPopup = false;
                            document.title = "";
                            document.title = app.currentPage.title;
                            if (typeof bk === "function") bk();
                        }

                        if (needObj.components.length === 1) {
                            that.skipPop = false;
                            document.title = "";
                            document.title = app.currentPage.title;
                            if (typeof bk === "function") bk();
                        } else history.go(-needObj.components.length + 1);
                    }
                    else {
                        var currentIndex = that.popIndex - 1;
                        var currentStorage = that.popUpStorages[currentIndex];
                        that.removeCurrentPopup();
                        var needObj = currentStorage.getNeedData();
                        if (location.protocol !== "file:") {
                            for (var i = 0; i < needObj.history.length; i++) {
                                history.pushState(needObj.datas[i], "", needObj.history[i]);
                            }
                        }
                        that.skipPop = true;
                        that.popBack = function () {
                            that.popBack = null;
                            that.skipPop = false;
                            that.hidenPopup = false;
                            if (typeof bk === "function") bk();
                        }

                        if (needObj.components.length === 1) {
                            that.skipPop = false;
                            if (typeof bk === "function") bk();
                        } else history.go(-needObj.components.length + 1);
                    }
                }
            this.hidenPopup = true;
            history.go(-popUpStorage.getIndex() - 2);
            popUpStorage.reset();
            popUpStorage = null;
            if (location.protocol == "file:") {
                if (typeof bk === "function") bk();
            }
        },
        initialize: function () {
            window.addEventListener("popstate", this.event.popHandler);
        },
        getLock: function () {
            if (!this.popUpStorage) return this.appStorage.isLock;
            return this.appStorage.isLock || this.popUpStorage.isLock;
        },
        restore: function () {
            this.appStorage.restore();
            if (this.popUpStorage) this.popUpStorage.restore();
        },
        setSkipPop: function (value) {
            this.skipPop = value;
        },
        setPopBack: function (popBack) {
            this.popBack = popBack;
        },
        _popHandler: function (ev) {
            var app = this.app,
                that = this;
            if (this.getLock() && !this.hidenPopup) {
                this.restore();
                return;
            }
            if (this.skipPop) {
                if (typeof this.popBack === "function") {
                    this.popBack();
                }
                return;
            }

            if (this.popUpStorages.length > 0) {
                var popUpStorage = this.popUpStorages[this.popIndex], popUp = that.popUps[this.popIndex]
                popUpStorage.popOperation(popUp, function (name, history) {
                    var popUrl = location.pathname + "?popup=" + name;
                    return history.indexOf(popUrl) === -1;
                }, function (component) {
                    component.parent.hidden();
                }, function (component, config, str) {
                    that.renderBackComponent(popUp, component, config, str);
                });
            } else {
                if (location.pathname == app.currentPage.url) return;
                this.appStorage.popOperation(app, function (name, nameList) {
                    return nameList.indexOf(name) === 0;
                }, function (component) {
                    if (typeof app.outofHistory === "function") app.outofHistory();
                    setTimeout(function () {
                        that.pushState(component, {});
                        document.title = component.title;
                        app.restorePage(component)
                    }, 2000);
                }, function (component, config, str) {
                    that.renderBackComponent(app, component, config, str);
                });
            }
        },
        renderBackComponent: function (target, component, config, str) {
            if (target.renderRefresh) {
                target.setRenderBack(str === "out");
                target.render(component.name, true, config);
            } else {
                if (component.isCompleteSave || typeof component.isCompleteSave === "undefined") {
                    document.title = component.title;
                    target.restorePage(component, config, str);
                }
                else {
                    target.setRenderBack(str === "out");
                    target.setRenderRefresh(true);
                    target.render(component.name, true, config);
                }
            }
        },
        pushState: function (page, option) {
            this.appStorage.pushState(page, option);
        },
        replaceState: function (page, option) {
            this.appStorage.replaceState(page, option);
        },
        setLock: function (isLock) {
            this.appStorage.setLock(isLock);
            if (this.popUpStorage) this.popUpStorage.setLock(isLock);
        }
    };

    function HistoryStorage(type) {
        this.type = type;
        this.history = [];
        this.components = [];
        this.datas = [];
        this.index = null;
        this.isLock = false;
    }
    HistoryStorage.prototype = {
        constructor: HistoryStorage,
        setLock: function (isLock) {
            this.isLock = isLock;
        },
        restore: function () {
            if (this.isLock) {
                var urlObj = this._getSurroundUrl();
                var name = this._getCurrentHistoryName();
                if (urlObj.prev === name) history.forward();
                else if (urlObj.next === name) history.back();
            }
        },
        reset: function () {
            for (var i = 0; i < this.components.length; i++) {
                var component = this.components[i];
                component.destroy(true);
            }
            this.history.length = 0;
            this.components.length = 0;
            this.datas.length = 0;
            this.index = null;
        },
        getNeedData: function () {
            return {
                history: this.history.slice(this.index),
                components: this.components.slice(this.index),
                datas: this.datas.slice(this.index)
            };
        },
        getLength: function () {
            return this.history.length;
        },
        setCurrentData: function (data, isNext) {
            var index = this.index;
            if (typeof isNext !== "undefined") {
                if (isNext) index++;
                else index--;
            }
            this.datas[index] = data;
        },
        initialize: function (name) {
            var type = this.type;
            var indexUrl = type === "app" ? "/str" : location.pathname + "?popup=" + name;
            this.history = [indexUrl];
            this.components = [null];
            this.datas = [{
                url: indexUrl
            }];
            if (location.protocol !== "file:") {
                history[type === "app" ? "replaceState" : "pushState"]({
                    url: indexUrl
                }, "", indexUrl);
            }
            this.index = 0;
        },
        pushState: function (component, option) {
            option = option || {};
            var url = this.type == "app" ? option.url || component.url : location.pathname + "?popup=" + component.name,
                components = this.components, com = components[this.index],
                datas = this.datas;

            if (com && typeof com.save === "function" && !com.isSave) com.save();
            if (location.protocol !== "file:") history.pushState(option, "", url);
            if (typeof this.index === "number") {
                for (var i = components.length - 1; i >= this.index + 1; i--) {
                    if (components[i] && typeof components[i].destroy === "function") components[i].destroy(true);
                }
                var nextIndex = ++this.index,
                    len = components.length;
                components.splice(nextIndex, len - nextIndex, component);
                datas.splice(nextIndex, len - nextIndex, option);
                this.history.splice(nextIndex, len - nextIndex, url);
            } else {
                this.index = 0;
                this.components.push(component);
                this.history.push(url);
                this.datas.push(option);
            }
        },
        replaceState: function (component, option) {
            if (this.type === "popup" && this.index === null) {
                return this.pushState(component, option);
            }
            option = option || {};
            var url = this.type == "app" ? option.url || component.url : location.pathname + "?popup=" + component.name,
                com = this.components[this.index];
            if (com && typeof com.destroy === "function") com.destroy(true);
            if (location.protocol !== "file:") history.replaceState(option, "", url);
            this.datas[this.index] = option;
            this.components[this.index] = component;
            this.history[this.index] = url;
        },
        getSurround: function () {
            var len = this.history.length;
            if (this.index === len - 1) {
                return {
                    next: "",
                    prev: this.history[len - 2]
                }
            } else {
                return {
                    next: this.history[this.index + 1],
                    prev: this.history[this.index - 1]
                }
            }
        },
        _getCurrentHistoryName: function () {
            if (this.type === "app") return location.pathname;
            else return this._getSearchName(location.search);
        },
        _getSurroundUrl: function () {
            var obj = this.getSurround();
            if (this.type === "popup" && obj) {
                if (obj.prev) obj.prev = this._getSearchName(obj.prev);
                if (obj.next) obj.next = this._getSearchName(obj.next);
            }
            return obj;
        },
        _getSearchName: function (str) {
            var index = str.indexOf("?");
            if (index > -1) {
                var list = str.slice(index + 1).split("=");
                for (var i = 0; i < list.length; i += 2) {
                    if (list[i] === "popup") return list[i + 1];
                }
            }
            return null;
        },
        getCurrentObj: function () {
            return {
                data: this.datas[this.index],
                url: this.history[this.index]
            }
        },
        getIndex: function () {
            var name = this._getCurrentHistoryName();
            if (!name) return -1;
            else {
                var components = this.components;
                for (var i = 0; i < components.length; i++) {
                    if (components[i].name === name) return i;
                }
            }
            return -1;
        },
        popOperation: function (target, filter, elseFn, operationFn) {
            var name = this._getCurrentHistoryName();
            if (filter(name, this.history)) {
                elseFn(this.components[this.index]);
            } else {
                var urlObj = this._getSurroundUrl(),
                    str, config,
                    component = this.components[this.index];

                if (urlObj.prev === name) {
                    if (typeof target.backFn === "function") {
                        target.execBack();
                        return false;
                    }
                    config = this.datas[--this.index];
                    str = "out";
                } else {
                    if (typeof target.forwardFn === "function") {
                        target.execForward();
                        return false;
                    }
                    config = this.datas[++this.index];
                    str = "in";
                }
                if (component && typeof component.save === "function") component.save(str === "out");
                operationFn(this.components[this.index], config, str);
            }
        },
        render: function (app, value, bk) {
            var vIndex = this.history.indexOf(value);
            if (vIndex != -1) {
                var currentIndex = this.index, component = this.components[vIndex],
                    config = this.datas[vIndex], str = vIndex > this.index ? "in" : "out", that = this;

                if (vIndex == this.index) return;
                if (app.currentPage == this.components[currentIndex] && this.components[currentIndex] && typeof this.components[currentIndex].save === "function") this.components[currentIndex].save();
                app.execFn(vIndex - currentIndex, function () {
                    app.GlobalHistory.renderBackComponent(app, component, config, str);
                    that.index = vIndex;
                    if (typeof bk === "function") bk();
                })
            }
            else {
                if (this.index === this.history.length - 1) app.render(value);
                else {
                    this.render(app, this.history[this.history.length - 1], function () {
                        app.render(value);
                        if (typeof bk === "function") bk();
                    })
                }
            }
        },
        go: function (app, num, bk) {
            var value = this.history[this.index + num];
            this.render(app, value, bk);
        },
        _removeIndex: function (app, index, bk) {
            var currentIndex = this.index, len = -1;
            if (currentIndex > 0) len = -2;
            app.execFn(len, function () {
                this.components[index].destroy(true);
                this.history.splice(index, 1);
                this.components.splice(index, 1);
                this.datas.splice(index, 1);
                this.index = index + len;
                for (var i = this.index + 1; i < this.datas.length; i++) {
                    history.pushState(this.datas[i], "", this.history[i]);
                }
                app.execFn(index - this.datas.length + len + 1, bk);
            }.bind(this));
        },
        remove: function (app, value, bk) {
            var index = this.history.indexOf(value), currentIndex = this.index,
                bkLen = this.index - this.datas.length + 1, len = index - currentIndex - 1;
            if (index > currentIndex) {
                bkLen += 1;
                len = -1;
            }
            var len = currentIndex > index ? (index - currentIndex - 1) : -1;
            if (index != -1) {
                app.execFn(len, function () {
                    this.components[index].destroy(true);
                    this.history.splice(index, 1);
                    this.components.splice(index, 1);
                    this.datas.splice(index, 1);
                    this.index += len;
                    for (var i = this.index + 1; i < this.datas.length; i++) {
                        history.pushState(this.datas[i], "", this.history[i]);
                        this.index++;
                    }
                    this.index += bkLen;
                    app.execFn(bkLen, bk);
                }.bind(this));
            }
        },
        renderAfterRemove: function (app, activeValue, value, bk) {
            if (activeValue == value) return;
            var index = this.history.indexOf(value);
            if (index !== -1) {
                this._removeIndex(app, index, function () {
                    this.render(app, activeValue, bk);
                }.bind(this));
            }
            else {
                app.render(app, activeValue, bk);
            }
        }
    }

    function ReplaceProto(name, staticName, currentName) {
        BaseProto.call(this);
        this.name = name;
        this.history = null;
        this.options = {};
        this.currentPage = null;
        this.staticPage = null;

        this.staticName = staticName;
        this.currentName = currentName;
        this.changeDom = null;
        this.backDom = null;
        this.parent = null;
        this.changeArea = null;
        this.data = {};
        this.tempData = {
            pageTemOption: {}
        };

        this.changeHandler = this._changeHandler.bind(this);
        this.renderFn = this._renderFn.bind(this);

        this.isRenderBack = false;
        this.renderRefresh = false;
        this.isRender = false;
        this.backFn = null;
        this.forwardFn = null;

        this.refreshFn = this._refreshFn.bind(this);
    }

    ReplaceProto.prototype = create(BaseProto.prototype, {
        setBackFn: function (feeback) {
            this.backFn = feeback;
        },
        setForwardFn: function (feeback) {
            this.forwardFn = feeback;
        },
        execBack: function (num) {
            num = num || 1;
            this.execFn(num, this.backFn);
            this.setBackFn(null);
        },
        execFn: function (num, bk) {
            if (num == 0) {
                if (typeof bk === "function") bk();
                return false;
            }
            var app = this._getApp();
            app.setSkipHistoryPop(true);
            app.setSkipPopBack(function () {
                app.setSkipHistoryPop(false);
                if (typeof bk === "function") bk();
            });
            history.go(num);
        },
        execForward: function (num) {
            num = num || 1;
            this.execFn(-num, this.forwardFn);
            this.setForwardFn(null);
        },
        _getApp: function () {
            return this;
        },
        saveData: function (data, isNext) {
            this.history.setCurrentData(data, isNext);
        },
        getActivePage: function (name) {
            if (this.currentPage.name === name) return this.currentPage;
            if (this.staticPage.name === name) return this.staticPage;
            if (this.currentPage.popUp) {
                var page = this.currentPage.popUp.getActivePage(name);
                if (page) return page;
            }
            if (this.staticPage.popUp) {
                var page = this.currentPage.popUp.getActivePage(name);
                if (page) return page;
            }
            if (this.popUps) {
                for (var i = 0; i < this.popUps.length; i++) {
                    var page = this.popUps[i].getActivePage(name);
                    if (page) return page;
                }
            }
        },
        setRenderRefresh: function (value) {
            this.renderRefresh = value;
        },
        setRenderBack: function (value) {
            this.isRenderBack = value;
        },
        restorePage: function (page, data, type) {
            if (page.constructor === Page) {
                if (page.parent.constructor === App) window.scrollTo(0, 0);
                var that = this;
                this.resetPopUp(function () {
                    page.data = data || history.state || {};
                    page.restore(that.backDom);
                    that.currentPage = page;
                    that.dispatchEvent({
                        type: "startexit"
                    });
                    that.setRenderBack(type === "out");
                    page._setDispose();
                    if (typeof page.setEnv === "function") page.setEnv();
                    that._replaceDom(data);
                });
            } else {
                this.setRenderBack(type === "out");
                this.render(page.url, true, data || history.state || {}, true);
            }
        },
        resetPopUp: function (bk) {
            if (typeof bk === "function") bk();
        },
        destroy: function () {
            this.eventDispatcher.destroy();
            this.currentPage.parent = null;
            this.staticPage.parent = null;
            this.currentPage.destroy(true);
            this.staticPage.destroy(true);
            this.changeArea = null;
            this.options = null;
            this.data = null;
            this.history.reset(true);
        },
        _show: function (bk) {
            var app = this._getApp(), that = this, len = 2;

            this.attachDiyEvent("enterend", function () {
                if (typeof that.enterEnd === "function") that.enterEnd();
                that.currentPage.dispatchEventThroughTree("enterend");
            });
            this.attachDiyEvent("startenter", function () {
                if (typeof that.startEnter === "function") that.startEnter();
                that.currentPage.dispatchEventThroughTree("startenter");
            });
            this.attachDiyEvent("exitend", function () {
                if (typeof that.exitEnd === "function") that.exitEnd();
                that.currentPage.dispatchEventThroughTree("exitend");
            });

            this.attachDiyEvent("startexit", function () {
                if (typeof that.startExit === "function") that.startExit();
                that.currentPage.dispatchEventThroughTree("startexit");
            });

            function feeback() {
                requestAnimationFrame(function () {
                    bk(that.staticPage, that.currentPage, app)
                });
            }
            [this.staticName, this.currentName].forEach(function (name, index) {
                app.getPageByName(name, function (outPage, opt) {
                    var page = new outPage();
                    if (index == 0) that.staticPage = page;
                    else that.currentPage = page;

                    page.baseUrl = getBaseUrl(opt.js);
                    for (var key in opt) {
                        if (["title", "js", "name", "url"].indexOf(key) === -1) page.data[key] = opt[key];
                    }
                    if (--len === 0) feeback();
                })
            })
        },
        setOptions: function (options) {
            this.options = extend(this.options, options);
        },
        _createOptionDom: function () {
            this.changeArea.innerHTML = "";
            var options = this.options;
            this.changeDom = document.createElement("div");
            this.changeDom.className = options.changeClass;
            this.backDom = document.createElement("div");
            this.backDom.className = "";
            this.changeArea.appendChild(this.changeDom);
            this.changeArea.appendChild(this.backDom);
        },
        render: function (pagename, isReplace, option) {
            if (this.isRender) return false;
            this.staticPage.dispatchEvent("startrender");
            this.isRender = true;
            var currentPage = this.currentPage, that = this;
            if (currentPage.popUp) {
                currentPage.popUp.hidden(null, function () {
                    that._render(pagename, isReplace, option);
                })
            }
            else {
                this._render(pagename, isReplace, option);
            }
        },
        _render: function (pagename, isReplace, option) {
            option = option || {};
            if (document.activeElement) document.activeElement.blur();
            var app = this._getApp();
            var that = this;
            app.getPageByName(pagename, this.renderFn, function (config) {
                var name = config.name;
                if (!that.tempData.pageTemOption[name]) that.tempData.pageTemOption[name] = {};
                that.tempData.pageTemOption[name].option = option;
                that.tempData.pageTemOption[name].isReplace = isReplace;
            });
        },
        _renderFn: function (outPage, opt) {
            if (opt.url === this.currentPage.url) return this.isRender = false;
            var page = new outPage();
            page.baseUrl = getBaseUrl(opt.js);
            var tempOption = this.tempData.pageTemOption[page.name];
            var option = tempOption.option,
                isReplace = tempOption.isReplace;
            for (var key in opt) {
                option[key] = option[key] || opt[key];
            }
            if (this.constructor === App || this.parent.constructor === Page) {
                this.history[isReplace ? "replaceState" : "pushState"](page, option);
                page.data = option;
            }
            this._renderPage(page, isReplace, option);
            delete this.tempData.pageTemOption[outPage.name];
        },
        _renderPage: function (pageObj, isReplace, option) {
            option = option || {};
            var that = this;
            this.dispatchEvent({ type: "startexit" });

            this.currentPage = pageObj;
            pageObj.parent = this;
            pageObj._setDispose();
            pageObj.render(function (html) {
                if (!App.configUrl(pageObj.url, location.pathname) && that.constructor !== PopUp) return;
                that.isRender = false;
                if (isReplace && !that.renderRefresh && !option.replace) {
                    pageObj.initialize(that.changeDom, html, null, function () {
                        that._renderDurationPage();
                        that.dispatchEvent({ type: "enterend" });
                        that.staticPage.dispatchEvent("renderhtml");
                    });
                } else {
                    var dom = that.backDom;
                    that._replaceDom(option);
                    pageObj.initialize(dom, html, null, function () {
                        that._renderDurationPage();
                        that.staticPage.dispatchEvent("renderhtml");
                    });
                }
            });
        },
        _renderDurationPage: function () {
            this.changeArea.scrollTop = 0;
            if (this.constructor === App) window.scrollTo(0, 0);
        },
        _changeHandler: function (ev) {
            var option = this.changeOption;
            var changeDom = this.changeDom;
            if (ev && ev.target !== changeDom) return false;
            var classObj = this._getReplaceClass(option),
                backDom = this.backDom;
            changeDom.className = classObj.changeStaticClass;
            backDom.className = "";
            backDom.innerHTML = "";
            removeClass(this.changeArea.classList, classObj.areaClass);
            this.setRenderBack(false);
            this.isRender = false;
            if (ev) {
                this.bindAnimation = false;
                this.changeOption = null;
                changeDom.removeEventListener("animationend", this.changeHandler, false);
                changeDom.removeEventListener("animationcancel", this.changeHandler, false);
                this.dispatchEvent({
                    type: "enterend"
                });
            }
        },
        _replaceDom: function (option) {
            this.setRenderRefresh(false);
            this.changeOption = option;
            if (this.bindAnimation) {
                this.changeDom.removeEventListener("animationend", this.changeHandler, false);
                this.changeDom.removeEventListener("animationcancel", this.changeHandler, false);
                this.dispatchEvent({
                    type: "enterend"
                });
            }
            var classObj = this._getReplaceClass(option);
            var that = this;
            this.changeDom.className = classObj.changeStaticClass;
            this.backDom.className = classObj.backStaticClass;
            var tempDom = this.backDom;
            this.backDom = this.changeDom;
            this.changeDom = tempDom;
            addClass(this.changeArea.classList, classObj.areaClass);
            var backDom = this.backDom, changeDom = this.changeDom;
            addClass(backDom.classList, classObj.backActiveClass);
            addClass(changeDom.classList, classObj.changeActiveClass);
            var style = window.getComputedStyle(changeDom),
                hasAnimation = Math.abs(parseFloat(style.animationDuration) - 0) > 0.0001;

            if (!hasAnimation) {
                this.changeHandler();
                this.dispatchEvent({
                    type: "enterend"
                });
            }
            else {
                this.bindAnimation = true;
                changeDom.addEventListener("animationend", this.changeHandler, false);
                changeDom.addEventListener("animationcancel", this.changeHandler, false);
                that.dispatchEvent({
                    type: "startenter"
                });
            }
        },
        refresh: function () {
            var app = this._getApp();
            if (this.currentPage.popUp) {
                this.currentPage.popUp.hidden(null, function () {
                    app.getPageByName(that.currentPage.name, that.refreshFn);
                })
            }
            else {
                app.getPageByName(this.currentPage.name, this.refreshFn);
            }
        },
        _refreshFn: function (page, config) {
            var newPage = new page();
            newPage.baseUrl = getBaseUrl(config.js);
            var data = this.currentPage.data;
            newPage.data = data;
            this.history.replaceState(newPage, data);
            this._renderPage(newPage, true, null, true);
        },
        renderBack: function (option) {
            this.renderTo(1, option);
        },
        renderTo: function (num, option) {
            this.setRenderBack(true);
            var app = this._getApp();
            app.GlobalHistory.backTo(num, option);
        },
        renderPageAfterNum: function (num, pagename, option) {
            var that = this;
            this.backAndRenderList(num, [], function () {
                that.render(pagename, false, option);
            });
        },
        backAndRenderList: function (num, list, bk) {
            var app = this._getApp();
            var that = this;
            if (num === 1) {
                app.setSkipHistoryPop(true);
                app.setSkipPopBack(function () {
                    app.setSkipHistoryPop(false);
                    app.setSkipPopBack(null);
                    for (var i = 0; i < list.length; i++) {
                        if (typeof list[i] === "string") {
                            that.history.pushState({ url: list[i] }, { url: list[i] });
                        }
                        else {
                            that.history.pushState({ url: list[i].url }, list[i].data);
                        }
                    }
                    if (typeof bk === "function") bk();
                });
                that.history.index--;
                return history.back();
            }
            app.GlobalHistory.backTo(num - 1, {}, function () {
                for (var i = 0; i < list.length; i++) {
                    that.history.pushState({ url: list[i] }, { url: list[i] });
                }
                if (typeof bk === "function") bk();
            })
        },
    });

    function App(name, staticName, currentName, version) {
        version = App.version = App.version || version || Math.random();
        this.version = version;
        ReplaceProto.call(this, name, staticName, currentName);
        this.animationList = new AnimationList();

        this.GlobalHistory = new History(this);
        this.GlobalHistory.initialize();

        this.history = new HistoryStorage("app");
        this.options = {
            changeClass: "app-change",
            backClass: "app-back",
            area: "change-state",
            in: {
                back: "page-out",
                change: "page-in"
            },
            out: {
                back: "page-in-reverse",
                change: "page-out-reverse"
            }
        };

        this.loadPageUnit = new LoadUnit(this.version);
        this.loadComponentUnit = new LoadUnit(this.version);
        this.loadPopUpUnit = new LoadUnit(this.version);

        this.filter = {};
        this.showPopups = [];
        this.isLock = false;

    }
    App.prototype = create(ReplaceProto.prototype, {
        constructor: App,
        setSkipHistoryPop: function (value) {
            this.GlobalHistory.setSkipPop(value);
        },
        setSkipPopBack: function (popBack) {
            this.GlobalHistory.setPopBack(popBack);
        },
        go: function (num, bk) {
            if (num == 0) return;
            var history = this.history;
            history.go(this, num, bk);
        },
        _prevAttachHistory: function (prevHistory) {
            var that = this;
            if (prevHistory && Array.isArray(prevHistory)) {
                for (var i = 0; i < prevHistory.length; i++) {
                    this.history.pushState({ url: prevHistory[i] }, { url: prevHistory[i] });
                }
                window.addEventListener("mousedown", initHandler, false);
                window.addEventListener("touchstart", initHandler, false);

                function initHandler(ev) {
                    window.removeEventListener("mousedown", initHandler, false);
                    window.removeEventListener("touchstart", initHandler, false);

                    if (location.protocol !== "file:") history.replaceState({}, "", location.pathname);
                }
            }
            window.addEventListener("click", function (ev) {
                var a = Component.getNodeName(ev.target, "A");
                if (a) {
                    var url = a.getAttribute("href");
                    if (url && url.indexOf("http") === -1) {
                        ev.preventDefault();
                        ev.stopPropagation();
                        that.render(url, a.target === "_self");
                    }
                }
            }, true);
        },
        initialize: function (body) {
            var that = this,
                body = body || document.body,
                prevHistory,
                preload = body.dataset.preload,
                pageContainer;

            if (preload === "true") {
                pageContainer = body.querySelector("[data-page]")
                this.currentName = pageContainer.dataset.page;
            }

            if (typeof that.getInitHistory === "function") {
                prevHistory = that.getInitHistory(location.pathname);
            }

            this._show(function (staticPage, currentPage) {
                that.GlobalHistory.appStorage = that.history;
                that.history.initialize();
                staticPage.parent = that;
                staticPage._setDispose();
                if (preload === "true") {
                    var activeHtml = pageContainer.innerHTML;
                    pageContainer.innerHTML = "";
                    var staticHtml = document.body.innerHTML;
                    if (staticPage.preload) staticPage.preload();
                    staticPage.initialize(body, staticHtml, {}, function () {
                        body.removeAttribute("data-preload");
                        that._initCurrentPage(staticPage, currentPage, prevHistory);
                        if (currentPage.preload) currentPage.preload();
                        currentPage._setDispose();
                        currentPage.initialize(that.changeDom, activeHtml, null, function () {
                            staticPage.dispatchEvent("renderhtml");
                        });
                    });
                }
                else {
                    staticPage.render(function (html) {
                        staticPage.initialize(body, html, {}, function () {
                            that._initCurrentPage(staticPage, currentPage, prevHistory)
                            currentPage._setDispose();
                            currentPage.render(function (html) {
                                currentPage.initialize(that.changeDom, html, null, function () {
                                    staticPage.dispatchEvent("renderhtml");
                                });
                            });
                        });
                    });
                }
            });
        },
        _initCurrentPage: function (staticPage, currentPage, prevHistory) {
            this.changeArea = staticPage.domList.pageContainer || body.querySelector(".change-container") || body;
            this._createOptionDom();
            this._prevAttachHistory(prevHistory);
            this.history.pushState(currentPage, {});
            currentPage.parent = this;
            document.title = currentPage.title;
        },
        removePopUpHistory: function (bk) {
            this.GlobalHistory.removePopUp(bk);
        },
        addAnimation: function (animation) {
            this.animationList.add(animation);
        },
        _getReplaceClass: function (option) {
            var options = this.options;
            option = option || {};
            return {
                backStaticClass: option.backClass || options.backClass,
                changeStaticClass: option.changeClass || options.changeClass,
                areaClass: option.area || options.area,
                backActiveClass: this.isRenderBack ? option.backClass || options.out.back : option.backClass || options.in.back,
                changeActiveClass: this.isRenderBack ? option.changeClass || options.out.change : option.changeClass || options.in.change
            }
        },
        showPopUp: function (popupName, data, isBack, isDismisBeforeShow, bk) {
            var that = this, data = data || {};
            this.getPopUpByName(popupName, function (popup) {
                var popUp = new popup(data.resetConfig);
                that._showPopUp(popUp, data, isBack, isDismisBeforeShow, bk);
            });
            return true;
        },
        _showPopUp: function (popUp, data, isBack, isDismisBeforeShow, bk) {
            var config, that = this, data = data || {};
            popUp.data = data;
            config = data.config || {
                in: {},
                out: {}
            };
            if (popUp.show(this.changeArea || this.staticPage.domList.pageContainer, config, this, isDismisBeforeShow)) {
                if (typeof bk === "function") bk(popUp);
                this.showPopups.push({
                    back: isBack,
                    popUp: popUp
                });
                popUp.hideBack = function (bk) {
                    popUp.destroy();
                    for (var i = 0; i < that.showPopups.length; i++) {
                        if (popUp === that.showPopups[i].popUp) that.showPopups.splice(i, 1);
                    }
                    if (typeof bk === "function") bk();
                }
            }
        },
        render: function (pagename, isReplace, option, isDoRplace) {
            var that = this;
            option = option || {};
            if (isDoRplace) option.replace = true;
            this.resetPopUp(function () {
                ReplaceProto.prototype.render.call(that, pagename, isReplace, option);
            });
        },
        _renderPage: function (pageObj, isReplace, option) {
            ReplaceProto.prototype._renderPage.call(this, pageObj, isReplace, option);
            document.title = pageObj.title;
        },
        lock: function () {
            this.isLock = true;
            this.history.setLock(true);
        },
        unlock: function () {
            this.isLock = false;
            this.history.setLock(false);
        },
        setPageConfig: function (configArray) {
            this.loadPageUnit.setConfig(configArray);
        },
        appendPageConfig: function (configArray) {
            if (Array.isArray(configArray)) {
                this.loadPageUnit.appendCondig(configArray);
            } else {
                this.loadPageUnit.appendCondig([configArray]);
            }
        },
        setComponentConfig: function (configArray) {
            this.loadComponentUnit.setConfig(configArray);
        },
        setPopUpConfig: function (configArray) {
            this.loadPopUpUnit.setConfig(configArray);
        },
        appendComponentConfig: function (configArray) {
            if (Array.isArray(configArray)) {
                this.loadComponentUnit.appendCondig(configArray);
            } else {
                this.loadComponentUnit.appendCondig([configArray]);
            }
        },
        appendPopUpConfig: function (configArray) {
            if (Array.isArray(configArray)) {
                this.loadPopUpUnit.appendCondig(configArray);
            } else {
                this.loadPopUpUnit.appendCondig([configArray]);
            }
        },
        getPopUpByName: function (name, next, beforeNext) {
            this.loadPopUpUnit.getUnit(name, next, beforeNext);
        },
        getComponentByName: function (name, next, beforeNext) {
            this.loadComponentUnit.getUnit(name, next, beforeNext);
        },
        getPageByName: function (name, next, beforeNext, afterNext) {
            this.loadPageUnit.getUnit(name, next, beforeNext, afterNext)
        },
        _defineComponent: function (name, bk, loadUnit, type, isNotWarn) {
            var script = document.querySelector("[data-name='" + name + "']");
            if (script) script.remove();
            else if (isNotWarn) console.warn("module: " + name + " is not required");

            var config = loadUnit._getConfigByName(name);
            if (typeof bk === "function") {
                loadUnit.units[name] = bk(config);
            }
            else {
                if (type === "component") loadUnit.units[name] = Component.extend(config.name, bk);
                else if (type === "page") loadUnit.units[name] = Page.extend(config.name, config.title, config.url, bk);
                else if (type === "popup") loadUnit.units[name] = PopUp.extend(config.name, config.staticName, config.activeName, config.config, bk);
            }
            loadUnit.dispatchEvent({ type: name, detail: { component: loadUnit.units[name], config: config } });
        },
        defineComponent: function (name, bk, isNotWarn) {
            this._defineComponent(name, bk, this.loadComponentUnit, "component", isNotWarn);
        },
        definePage: function (name, bk, isNotWarn) {
            this._defineComponent(name, bk, this.loadPageUnit, "page", isNotWarn);
        },
        definePopUp: function (name, bk, isNotWarn) {
            this._defineComponent(name, bk, this.loadPopUpUnit, "popup", isNotWarn);
        },
        addFilter: function (name, fn) {
            if (!name in this.filter) {
                console.warn("已经加入该名称的过滤器，请确认是否把之前的覆盖掉了");
            }
            this.filter[name] = fn;
        },
        removeFilter: function (name) {
            if (name in this.filter) {
                delete this.filter[name];
            }
        },
        resetPopUp: function (bk) {
            var backPops = [];
            for (var i = 0; i < this.showPopups.length; i++) {
                var popConfig = this.showPopups[i];
                if (!popConfig.back && !popConfig.popUp.isHidden) backPops.push(popConfig);
            }
            var len = backPops.length;
            if (len === 0 && typeof bk === "function") return bk();
            for (var i = 0; i < backPops.length; i++) {
                backPops[i].popUp.hidden(null, function () {
                    len--;
                    if (len === 0 && typeof bk === "function") bk();
                });
            }
        },
        removeAnimationByTarget: function (htmlProto) {
            this.animationList._removeByTarget(htmlProto.uid);
        }
    });

    App.setCommonHeader = function (key, value) {
        Http.commonHeader[key] = value;
    }
    App.list = {}

    function getSearchParam(url) {
        var result = {};
        if (url) {
            var list = url.split("&");
            for (var i = 0; i < list.length; i++) {
                var koList = list[i].split("=");
                var key = decodeURIComponent(koList[0]), value = decodeURIComponent(koList[1]);
                result[key] = value;
            }
        }
        return result;
    }

    App.configUrl = function (u, url) {
        if (!url) return false;
        var urlArray = url.split("?");
        var result = getSearchParam(urlArray[1]);
        url = urlArray[0];
        var originList = u.split("/"),
            targetList = url.split("/");
        var len = originList.length,
            oStr;
        if (len !== targetList.length) return false;
        for (var i = 0; i < len; i++) {
            var oStr = originList[i],
                rStr = targetList[i];
            if (oStr.indexOf(":") === 0) {
                result[oStr.slice(1)] = targetList[i];
                originList[i] = targetList[i];
            } else if (oStr !== rStr) return false;
        }
        result.url = url;
        result.originUrl = u;
        return result;
    }
    App.joinUrl = function (url, obj) {
        return url.replace(/:[^\/]*/g, function (match) {
            return obj[match.slice(1)];
        })
    };

    App.create = (function () {
        return function (name, staticName, currentName, isTest) {
            if (name in App.list) {
                var app = App.list[name];
                app.staticName = staticName || app.staticName;
                app.currentName = currentName || app.currentName;
                App.current = app;
                return App.list[name];
            }

            var app = App.list[name] = new App(name, staticName, currentName, isTest);
            App.current = app;
            return app;
        }
    })();

    var loadUnit = new LoadUnit();
    App.serviceBase = "/public/services/";
    App.serviceConfig = [];
    App.setServiceConfig = function (list) {
        App.serviceConfig = list;
    };
    App._contains = function (name) {
        var serviceConfig = App.serviceConfig;
        for (var i = 0; i < serviceConfig.length; i++) {
            if (serviceConfig[i].name === name) return serviceConfig[i];
        }
        return false;
    };
    App.appendServiceConfig = function (list) {
        for (var i = 0; i < list.length; i++) {
            var name = list[i].name;
            if (!this._contains(name)) console.warn("service config: " + name + "has exists.");
            App.serviceConfig.push(list[i]);
        }
    };
    App.setServiceBase = function (path) {
        this.serviceBase = path;
    };
    App.define = function (name, bk, isNotWarn) {
        var script = document.querySelector("[data-name='" + name + "']");
        if (script) script.remove();
        else if (!isNotWarn) console.warn("service: " + name + " is not required !");
        if (typeof bk === "function") loadUnit.units[name] = bk();
        else loadUnit.units[name] = bk;
        loadUnit.dispatchEvent({ type: name, detail: { component: loadUnit.units[name] } })
    };
    App.getLoadUnit = function (name) {
        return loadUnit.units[name];
    };
    App.require = function (list, bk) {
        if (typeof list === "function") return list.call(this);
        var len = list.length, mods = [], that = this;
        if (len == 0) bk.call(this);
        var func = function (obj, index) {
            mods[index] = obj;
            if (--len === 0) bk.apply(that, mods);
        }

        list.forEach(function (item, index) {
            var config = that._contains(item);
            loadUnit._getUnit({ name: item, js: config ? config.js + "?v=" + App.version : App.serviceBase + item + ".js" + "?v=" + App.version }, function (obj) {
                func(obj, index);
            })
        })
    };

    App.getCurrent = function () {
        return App.current;
    };
    App.setHttpCache = function (url, str) {
        Http.cache.setInstance(url, str);
    }

    App.extend = extend;

    function PopUp(name, staticName, currentName) {
        ReplaceProto.call(this, name, staticName, currentName);

        this.history = new HistoryStorage("popup");
        this.options = {
            className: "popup",
            changeClass: "popup-change",
            backClass: "popup-back",
            area: "popup-state",
            currentIn: {
                backClass: "popup-active-out",
                changeClass: "popup-active-in"
            },
            currentOut: {
                backClass: "popup-active-in-reverse",
                changeClass: "popup-active-out-reverse"
            },
            staticIn: "popup-static-out",
            staticOut: "popup-static-in"
        };

        this.isShow = false;
        this.config = null;
        this.hideBack = null;
        this.popDiv = document.createElement("div");
        this.relativeDom = null;
        this.enterHandler = this._enterHandler.bind(this);
    };
    PopUp.prototype = create(ReplaceProto.prototype, {
        constructor: PopUp,
        _enterHandler: function (ev) {
            if (ev.keyCode == 27) {
                if (this.parent.constructor === Page && !this.currentPage.popUp) this.hidden();
            }
        },
        _getApp: function () {
            var parent = this;
            while (parent && parent.constructor !== App) {
                parent = parent.parent;
            }
            return parent;
        },
        show: function (dom, config, target, isDismisBeforeShow) {
            this.parent = target;
            this.config = config;
            if (!isDismisBeforeShow && typeof this.beforeShow === "function") {
                if (!this.beforeShow(config, target)) return false;
            }

            var that = this,
                popDiv = this.popDiv
            this.relativeDom = dom;
            var classIn = config.staticIn || this.options.staticIn;
            addClass(popDiv.classList, classIn);
            addClass(popDiv.classList, this.options.className);
            dom.parentNode.appendChild(popDiv);
            this.isShow = true;
            this._show(function (staticPage, currentPage, app) {
                window.addEventListener("keydown", that.enterHandler, false);
                if (target.constructor === Page) {
                    app.GlobalHistory.addPopUp(that);
                }
                staticPage.parent = that;
                currentPage.parent = that;
                staticPage.render(function (html) {
                    staticPage._setDispose();
                    staticPage.initialize(popDiv, html, null, function () {
                        that.changeArea = staticPage.domList.pageContainer || popDiv;
                        that._createOptionDom();
                        var changeDom = that.changeDom;
                        currentPage.render(function (htmlstr) {
                            if (target.constructor === Page) that.history.replaceState(currentPage, config);
                            currentPage.initialize(changeDom, htmlstr, null, function () {
                                staticPage.dispatchEvent("renderhtml");
                            });
                        });

                        var style = window.getComputedStyle(popDiv),
                            hasAnimation = Math.abs(parseFloat(style.animationDuration) - 0) > 0.0001;

                        if (hasAnimation) {
                            that.dispatchEvent({
                                type: "startenter"
                            });
                            var hiddenHandler = function (ev) {
                                if (ev.target !== popDiv) return false;
                                removeClass(popDiv.classList, classIn);
                                popDiv.removeEventListener("animationend", hiddenHandler, false);
                                popDiv.removeEventListener("animationcancel", hiddenHandler, false);
                                that.dispatchEvent({
                                    type: "enterend"
                                });
                            }
                            popDiv.addEventListener("animationend", hiddenHandler, false);
                            popDiv.addEventListener("animationcancel", hiddenHandler, false);
                        }
                        else {
                            that.dispatchEvent({
                                type: "enterend"
                            });
                        }
                    });
                })
            });
            return true;
        },
        _hidden: function (option, bk) {
            this.dispatchEvent({
                type: "startexit"
            });
            option = option || {};
            var that = this,
                popDiv = this.popDiv,
                changeDom = this.changeDom,
                outClass = option.staticOut || this.config.staticOut || this.options.staticOut,
                pageOutClass = option.currentIn || this.config.currentIn || this.options.currentIn.changeClass;

            addClass(popDiv.classList, outClass);
            addClass(changeDom.classList, pageOutClass);

            var style = window.getComputedStyle(popDiv),
                hasAnimation = Math.abs(parseFloat(style.animationDuration) - 0) > 0.0001;

            if (typeof this.hideBack === "function") {
                if (hasAnimation) {
                    var hiddenHandler = function (ev) {
                        if (ev.target !== popDiv) return;
                        removeClass(popDiv.classList, outClass);
                        removeClass(changeDom.classList, pageOutClass);
                        popDiv.removeEventListener("animationend", hiddenHandler, false);
                        popDiv.removeEventListener("animationcancel", hiddenHandler, false);
                        that.dispatchEvent({
                            type: "exitend"
                        });
                        that.hideBack(bk);
                    }
                    popDiv.addEventListener("animationend", hiddenHandler, false);
                    popDiv.addEventListener("animationcancel", hiddenHandler, false);
                } else {
                    this.hideBack(bk);
                }
            }
        },
        hidden: function (option, bk) {
            var that = this;
            if (this.isHidden) return;
            if (!this.isShow) {
                if (typeof bk === "function") bk();
                return;
            };
            this.isHidden = true;
            window.removeEventListener("keydown", this.enterHandler, false);
            if (this.currentPage.popUp) {
                this.currentPage.popUp.hidden(null, function () {
                    requestAnimationFrame(function () {
                        that._hidden(option, bk);
                    });
                });
            }
            else {
                requestAnimationFrame(function () {
                    that._hidden(option, bk);
                });
            }
        },
        destroy: function (isClearPage) {
            ReplaceProto.prototype.destroy.call(this, isClearPage);
            this.relativeDom = null;
            if (this.popDiv && this.popDiv.parentNode) this.popDiv.parentNode.removeChild(this.popDiv);
            this.config = null;
            this.popDiv = null;
            this.isShow = false;
        },
        dispatchEventByName: function (name, data, target) {
            target = target || this.parent;
            data = data || {};
            var result = { popUp: this };
            App.extend(App.extend(result, this.data), data);
            target.dispatchEvent(name, result);
        },
        _getReplaceClass: function (option) {
            var options = this.options;
            option = option || {};
            return {
                backStaticClass: option.backClass || options.backClass,
                changeStaticClass: option.changeClass || options.changeClass,
                areaClass: option.area || options.area,
                backActiveClass: this.isRenderBack ?
                    option.backClass || options.currentOut.backClass : option.backClass || options.currentIn.backClass,
                changeActiveClass: this.isRenderBack ?
                    option.changeClass || options.currentOut.changeClass : option.changeClass || options.currentIn.changeClass
            }
        }
    });

    PopUp.extend = function (name, staticName, activeName, config, option) {
        function PopUpExtend(data) {
            data = data || {};
            PopUp.call(this, name, staticName, data.activeName || activeName);
            this.setOptions(config);
        }
        PopUpExtend.prototype = create(PopUp.prototype, option);

        return PopUpExtend;
    };

    App.join = function (url) {
        if (typeof __dirname === "string") return require("path").join(__dirname, url);
        return url;
    };

    function setDefault(target, origin) {
        target = target || {}, origin = origin || {};
        for (var key in origin) {
            if (typeof target[key] === "undefined") target[key] = origin[key];
        }
        return target;
    }

    App.serialize = serialize;
    App.ViewerOption = { width: 1920, height: 1080 };
    App.Viewer = function (canvas, option) {
        BaseProto.call(this);
        this.uid = createUid();
        this.canvas = canvas;
        this.option = setDefault(option, App.ViewerOption);
        this.data = {};
        this.animationList = new AnimationList();
        // this.currentScene = new App.Scene(this.option.name, this.option.defaultScene);
        // this.currentScene.parent = this;
        this.scenes = [];
        this.backScenes = [];
        this.globalWidgets = [];
    }
    App.Viewer.prototype = create(BaseProto.prototype, {
        constructor: App.Viewer,
        createUrl: function () {
            return this.canvas.toDataURL();
        },
        destroy: function () {
            if (typeof this.dispose === "function") this.dispose();
            this.eventDispatcher.destroy();
            for (var i = this.scenes.length - 1; i >= 0; i--) {
                this.removeScene(this.scenes[i]);
            }
            this.data = {};
            this.animationList.destroy();
        },
        addWidget: function (widget) {
            for (var i = 0; i < this.globalWidgets.length; i++) {
                if (this.globalWidgets[i].name == widget.name) return this;
            }
            this.globalWidgets.push(widget);
            return this;
        },
        getGlobalWidgetByName: function (name) {
            var widgets = this.globalWidgets;
            for (var i = 0; i < widgets.length; i++) {
                if (widgets[i].name === name) return widgets[i];
            }
            return null;
        },
        // get currentScene() {
        //     return this.scenes[this.scenes.length - 1];
        // },
        initialize: function () {
            var option = this.option, canvas = this.canvas;
            canvas.width = option.width;
            canvas.height = option.height;
            // this.currentScene.canvas = canvas;

            var animation = new Animation(10, 0, Animation.normal), that = this;
            animation.onUpdate(function (time) {
                that.render();
            });
            this.addAnimation(animation);

            this.mouseDownHandler = this._mousedownHandler.bind(this);
            this.mouseMoveHandler = this._mousemoveHandler.bind(this);
            this.mouseUpHandler = this._mouseupHandler.bind(this);
            this.clickHandler = this._clickHandler.bind(this);
            this.keyPressHandler = this._keyPressHandler.bind(this);
            this.keyUpHandler = this._keyUpHandler.bind(this);
            this.keyDownHandler = this._keyDownHandler.bind(this);
            canvas.addEventListener("mousedown", this.mouseDownHandler, false);
            canvas.addEventListener("mousemove", this.mouseMoveHandler, false);
            canvas.addEventListener("mouseup", this.mouseUpHandler, false);
            canvas.addEventListener("click", this.clickHandler, false);
            window.addEventListener("keypress", this.keyPressHandler, false);
            window.addEventListener("keydown", this.keyDownHandler, false);
            window.addEventListener("keyup", this.keyUpHandler, false);
        },
        _keyDownHandler: function (ev) {
            var widgets = this.getCurrentSceneWidgets();
            var event = this._wrapEvent(ev);
            for (var i = 0; i < widgets.length; i++) {
                widgets[i].dispatchAllEvent("keydown", event);
            }
        },
        _keyUpHandler: function (ev) {
            var widgets = this.getCurrentSceneWidgets();
            var event = this._wrapEvent(ev);
            for (var i = 0; i < widgets.length; i++) {
                widgets[i].dispatchAllEvent("keyup", event);
            }
        },
        _keyPressHandler: function (ev) {
            var widgets = this.getCurrentSceneWidgets();
            var event = this._wrapEvent(ev);
            for (var i = 0; i < widgets.length; i++) {
                widgets[i].dispatchAllEvent("keypress", event);
            }
        },
        _wrapEvent: function (ev) {
            var target = ev.target, rect = target.getBoundingClientRect();
            return {
                x: ev.clientX - rect.left,
                y: ev.clientY - rect.top,
                originEvent: ev
            }
        },
        _mousedownHandler: function (ev) {
            var widgets = this.getCurrentSceneWidgets();
            var event = this._wrapEvent(ev);
            for (var i = 0; i < widgets.length; i++) {
                widgets[i].dispatchAllEvent("mousedown", event);
            }
        },
        getCurrentSceneWidgets: function () {
            return this.currentScene.widgets;
        },
        _mousemoveHandler: function (ev) {
            var widgets = this.getCurrentSceneWidgets();
            var event = this._wrapEvent(ev);
            for (var i = 0; i < widgets.length; i++) {
                widgets[i].dispatchAllEvent("mousemove", event);
            }
        },
        _mouseupHandler: function (ev) {
            var widgets = this.getCurrentSceneWidgets();
            var event = this._wrapEvent(ev);
            for (var i = 0; i < widgets.length; i++) {
                widgets[i].dispatchAllEvent("mouseup", event);
            }
        },
        _clickHandler: function (ev) {
            var widgets = this.getCurrentSceneWidgets();
            var event = this._wrapEvent(ev);
            for (var i = 0; i < widgets.length; i++) {
                widgets[i].dispatchAllEvent("click", event);
            }
        },
        addAnimation: function (animation) {
            this.animationList.add(animation);
        },
        removeAnimationByTarget: function (uid) {
            this.animationList._removeByTarget(uid);
        },
        addScene: function (scene, isRender) { // 添加场景，是否直接渲染
            scene.isRender = isRender;
            scene.parent = this;
            scene.canvas = this.canvas;
            this.currentScene = scene;
            this.scenes.push(scene);
        },
        changeScene: function (newScene, oldScene, isRender) {
            var oldIndex = this.scenes.indexOf(oldScene);
            if (oldIndex !== -1) {
                newScene.isRender = isRender;
                this.scenes.splice(oldIndex, 1, newScene);
                oldScene.destroy();
            }
            else {
                this.addScene(scene, isRender);
            }
        },
        removeScene: function (scene) {
            var obj = this._getIndexByUid(scene);
            if (obj.index !== -1) this.scenes.splice(obj.index, 1);
            if (obj.scene) obj.scene.destroy();
        },
        _getIndexByUid: function (scene) {
            if (typeof scene === "object") {
                return {
                    index: this.scenes.indexOf(scene),
                    scene: scene
                }
            }
            else {
                var scenes = this.scenes;
                for (var i = 0; i < scenes.length; i++) {
                    if (scenes[i].uid == scene) return {
                        index: i,
                        scene: scenes[i]
                    };
                }
            }
        },
        _addSceneObj: function (sceneObj, wm, wbm, scene) {
            for (var i = 0; i < sceneObj.widgets.length; i++) {
                var wd = sceneObj.widgets[i];
                if (wd.mockData) wd.style.option = wd.mockData;
                var widget = wm.addWidget(wd.type, wd.style, scene, sceneObj.resources);
                if (wd.behaviors) {
                    for (var j = 0; j < wd.behaviors.length; j++) {
                        var bh = wd.behaviors[j];
                        wbm.addBehavior(bh.type, bh.style, widget);
                    }
                }
            }
            if ("marker" in sceneObj) {
                for (var key in sceneObj.marker) {
                    var option = sceneObj.marker[key];
                    var widget = wm.addWidget(option.type, option.style, this);
                    widget.name = key;
                }
            }
        },
        renderScene: function (sceneObj, wm, wbm) {
            this.scenes.length = 0;
            var scene;
            if (sceneObj instanceof App.Scene) {
                scene = sceneObj;
                this.addScene(scene, true);
            }
            else {
                scene = new App.Scene(sceneObj.name, sceneObj.option);
                this.addScene(scene, true);
                this._addSceneObj(sceneObj, wm, wbm, scene);
            }
            return scene;
        },
        previewScene: function (scenes, wm, wbm) {
            this.backScenes = this.scenes.slice(0);
            this.scenes.length = 0;
            if (scenes.length > 0) {
                for (var i = 0; i < scenes.length; i++) {
                    var sceneObj = scenes[i];
                    var scene = new App.Scene(sceneObj.name, sceneObj.option);
                    this.addScene(scene, true);
                    this._addSceneObj(sceneObj, wm, wbm, scene);
                }
            }
            else {
                this._clear();
            }
        },
        backPreview: function () {
            var scenes = this.scenes;
            for (var i = scenes.length - 1; i >= 0; i--) {
                var scene = this.scenes[i];
                scene.destroy();
                scenes.splice(i , 1);
            }
            this.scenes = this.backScenes.slice(0);
            this.currentScene = this.scenes[this.scenes.length - 1];
            this.backScenes.length = 0;
            this.render(true);
        },
        _clear: function () {
            var canvas = this.canvas;
            var ctx = canvas.getContext("2d");
            ctx.clearRect(0, 0, canvas.width, canvas.height);
        },
        _render: function () {
            this._clear();
            for (var i = 0; i < this.scenes.length; i++) {
                this.scenes[i].render();
            }
        },
        render: function (isStrongRender) {
            if (isStrongRender) {
                this._render();
            }
            else {
                var isRender = false;
                for (var i = 0; i < this.scenes.length; i++) {
                    isRender = isRender || this.scenes[i].isRender;
                }
                if (isRender) {
                    this._render();
                }
            }
        },
        exportSvg: function () { },
        exportJPG: function () { },
    });
    App.Viewer.create = function (canvas, option, sceneManager, scenes, sceneOptions) {
        var viewer = new App.Viewer(canvas, option);
        if (!Array.isArray(scenes)) scenes = [scenes];
        if (!Array.isArray(sceneOptions)) sceneOptions = [sceneOptions];

        viewer.initialize();
        for (var i = 0; i < scenes.length; i++) {
            sceneManager.addScene(scenes[i], sceneOptions[i], viewer);
        }
        return viewer;
    }
    App.Viewer.from = function (canvas, obj, wm, wbm) {
        var option = { width: obj.width, height: obj.height };
        var viewer = new App.Viewer(canvas, option);
        viewer.initialize();
        
        function renderSelectScenes() {
            var selectScenes = obj.selectScenes[viewer.selectIndex].scenes;
            var renderScenes = [];
            var scenes = viewer.scenes;
            var _selectScenes = [];
            for (var i = 0; i < selectScenes.length; i++) {
                var s = selectScenes[i];
                for (var j = 0; j < obj.scenes.length; j++) {
                    if (obj.scenes[j].uid === s) {
                        _selectScenes.push(obj.scenes[j])
                        break;
                    }
                }
            }
            for (var i = scenes.length - 1; i >= 0; i--) {
                var scene = scenes[i], isHas = false;

                for (var j = 0; j < _selectScenes.length - 1; j++) {
                    var selectScene = _selectScenes[j];
                    if (scene.uid == selectScene.uid) {
                        isHas = true;
                        break;
                    }
                }
                if (!isHas) viewer.removeScene(scene);
            }
            var hasScenes = viewer.scenes;
            for (var i = 0; i < _selectScenes.length; i++) {
                var selectScene = _selectScenes[i], isHas = false;
                for (var j = 0; j < hasScenes.length; j++) {
                    var hScene = hasScenes[j];
                    if (hScene.uid == selectScene.uid) {
                        isHas = true;
                        renderScenes.push(hScene);
                        break;
                    }
                }
                if (!isHas) renderScenes.push(selectScene);
            }

            viewer.scenes.length = 0;
            for (var i = 0; i < renderScenes.length; i++) {
                var rScene = renderScenes[i];
                if (rScene instanceof App.Scene) {
                    viewer.addScene(rScene, true);
                }
                else {
                    var scene = new App.Scene(rScene.name, rScene.option, rScene.uid);
                    viewer.addScene(scene, true);
                    rScene.resources = obj.resources;
                    rScene.marker = obj.markers;
                    for (var ii = 0; ii < rScene.widgets.length; ii++) {
                        var wd = rScene.widgets[ii];
                        if (wd.mockData) wd.style.option = wd.mockData;
                        var widget = wm.addWidget(wd.type, wd.style, scene, rScene.resources);
                        if (wd.behaviors) {
                            for (var j = 0; j < wd.behaviors.length; j++) {
                                var bh = wd.behaviors[j];
                                wbm.addBehavior(bh.type, bh.style, widget);
                            }
                        }
                    }
                    if ("marker" in rScene) {
                        for (var key in rScene.marker) {
                            var option = rScene.marker[key];
                            var widget = wm.addWidget(option.type, option.style, viewer, null, key);
                            widget.name = key;
                        }
                    }
                }
            }
        }

        viewer.selectIndex = 0;
        viewer.previousScene = function () {
            if (viewer.selectIndex == 0) viewer.selectIndex = obj.selectScenes.length - 1;
            else viewer.selectIndex--;
            renderSelectScenes()
        };
        viewer.nextScene = function () {
            if (viewer.selectIndex == obj.selectScenes.length - 1) viewer.selectIndex = 0;
            else viewer.selectIndex++;
            renderSelectScenes()
        };
        renderSelectScenes();
        return viewer;
    };

    // 代表屏幕显示的内容，也可以播放声音，其它有关于当前显示任何的东西
    App.SceneOption = {};
    App.Scene = function (name, option, uid) {
        BaseProto.call(this);
        this.uid = uid || createUid();
        this.name = name;
        this.option = setDefault(option, App.SceneOption);
        this.widgets = [];
        this.data = {};
        this.parent = null;
        this.canvas = null;
        this.ctx = null;
        this.isRender = false;
        this.activeWidget = null;
    }
    App.Scene.prototype = create(BaseProto.prototype, {
        constructor: App.Scene,
        getContext: function () {
            return this.canvas.getContext("2d");
        },
        toObject: function (fb) {
            var obj = { 
                name: this.name, 
                option: this.option, 
                widgets: [], 
                marker: {},
                resources: {} 
            };
            var widgets = this.widgets;
            for (var i = 0; i < widgets.length; i++) {
                var wid = widgets[i];
                obj.widgets.push(wid.toObject());
                if (wid.marker) 
                    obj.marker[wid.marker] = this.parent.getGlobalWidgetByName(wid.marker).toObject();
            }
            if (this.parent.currentScene === this) obj.url = this.canvas.toDataURL();
            else obj.url = this.url;
            obj.title = this.title;
            obj.description = this.description;
            obj.uid = this.uid;
            fb(obj);
            return this;
        },
        destroy: function () {
            if (typeof this.dispose === "function") this.dispose();
            this.eventDispatcher.destroy();

            for (var i = this.widgets.length - 1; i >= 0; i--) {
                this.removeWidget(this.widgets[i]);
            }
            this.data = {};
            this.parent = null;
            this.canvas = null;
            this.activeWidget = null;
        },
        addWidget: function (widget, isRender) {
            this.widgets.push(widget);
            widget.parent = this;
            if (isRender) this.nextRender();
            return this;
        },
        removeWidget: function (widget) {
            var index = this.widgets.indexOf(widget);
            if (index !== -1) this.widgets.splice(index, 1);
            var event = document.createEvent("CustomEvent");
            event.initCustomEvent("removewidget", true, true,  {
                widget: widget
            });
            this.canvas.dispatchEvent(event);
            widget.destroy();
            this.nextRender();
        },
        render: function () {
            if (this.isRender) this.isRender = false;
            var canvas = this.canvas;
            var ctx = canvas.getContext("2d");
            
            for (var i = 0; i < this.widgets.length; i++) this.widgets[i].renderTree(ctx);
        },
        _initialize: function () {
            this.initialize();
        },
        playAudio: function () { },
        nextRender: function () {
            this.isRender = true;
        }
    });

    App.Scene.extend = function (name, option) {
        function SceneExtend(style) {
            App.Scene.call(this, name, style);
        }
        SceneExtend.prototype = create(App.Scene.prototype, option);
        return SceneExtend;
    };

    App.Widget = function (type, style) {
        BaseProto.call(this);
        this.uid = createUid();
        this.type = type;
        this.data = {};
        this.style = style; // 代表渲染方式 
        this.parent = null;
        this.widgets = [];
        this.updatePropertys = {};
        this.behaviors = []; // 添加编辑器，可以更改属性，添加可视化操作, 可以为特定元素的编辑器，也可以是附加的通用编辑器
    };
    App.Widget.prototype = create(BaseProto.prototype, {
        constructor: App.Widget,
        initialize: function () { },
        toObject: function () {
            var obj = { 
                type: this.type, 
                style: this.style, 
                mockData: this.style.option, 
                behaviors: [],
                resourceKey: this.resourceKey
            };
            var behs = this.behaviors;
            for (var i = 0; i < behs.length; i++) {
                obj.behaviors.push(behs[i].toObject());
            }
            return obj;
        },
        removeAnimation: function () {
            var viewer = this.getViewer();
            if (viewer) viewer.removeAnimationByTarget(this.uid);
        },
        destroy: function () {
            if (typeof this.dispose === "function") this.dispose();
            this.eventDispatcher.destroy();
            this.removeAnimation();

            for (var i = this.widgets.length - 1; i >= 0; i--) {
                this.removeWidget(this.widgets[i]);
            }

            for (var i = this.behaviors.length - 1; i >= 0; i--) {
                this.removeBehavior(this.behaviors[i]);
            }
            this.data = {};
            this.style = {};
            this.parent = null;
            this.updatePropertys = {};
        },
        _clickEditArea: function (ev) {
            return true;
        },
        updatePropertyElement: function (obj) {
            this.updatePropertys = obj;
        },
        updatePropertyExcept: function (key, value, element) {
            if (typeof value === "string" && value.indexOf("resource") == 0) value = App.ResourceManager.get(value);
            this[key] = value;
            var propertys = this.updatePropertys;
            if (propertys && propertys[key]) {
                for (var i = 0; i < propertys[key].length; i++) {
                    var targetElement = propertys[key][i];
                    if (targetElement !== element) targetElement.value = value;
                }
            }
        },
        createEditForm: function () {
            var behaviors = this.behaviors, result = [];
            for (var i = 0; i < behaviors.length; i++) {
                var behavior = behaviors[i];
                if (behavior.toForm) {
                    result.push({
                        name: behavior.name,
                        detail: behavior.toForm,
                        behavior: behavior
                    })
                }
            }
            return result;
        },
        renderTree: function (ctx) {
            ctx.save();
            this.render(ctx);
            for (var i = 0; i < this.widgets.length; i++) this.widgets[i].renderTree(ctx);
            for (var i = 0; i < this.behaviors.length; i++) this.behaviors[i].render(ctx);
            ctx.restore();
        },
        addWidget: function (widget, isRender) {
            this.widgets.push(widget);
            widget.parent = this;
            if (isRender) this.getScene().nextRender();
        },
        removeWidget: function (widget) {
            var index = this.widgets.indexOf(widget);
            if (index !== -1) this.widgets.splice(index, 1);
            widget.destroy();
        },
        getScene: function () {
            var parent = this.parent;
            while (parent.constructor && parent.constructor !== App.Scene) {
                parent = parent.parent;
            }
            return parent;
        },
        addBehavior: function (behavior, isRender) {
            this.behaviors.push(behavior);
            behavior.target = this;
            if (isRender) this.getScene().nextRender();
        },

        removeBehavior: function (behavior) {
            var index = this.behaviors.indexOf(behavior);
            if (index !== -1) this.behaviors.splice(index, 1);
            behavior.destroy();
        },
        dispatchAllEvent: function (name, obj) {
            var behaviorPatch = this.dispatchBehaviorEvent(name, obj);
            if (!behaviorPatch) {
                for (var i = 0; i < this.widgets.length; i++) {
                    if (obj.isStopNextWidget) break;
                    this.widgets[i].dispatchAllEvent(name, obj);
                }
                if (!obj.isStopPropagation) this.dispatchEvent(name, obj);
            }
        },
        addAnimation: function (animation) {
            animation.target = this.uid;
            var viewer = this.getViewer();
            if (viewer) viewer.addAnimation(animation);
        },
        removeAnimation: function () {
            var viewer = this.getViewer();
            if (viewer) viewer.removeAnimationByTarget(this.uid);
        },
        getViewer: function () {
            var scene = this.getScene();
            if (scene) return scene.parent;
        },
        dispatchBehaviorEvent: function (name, obj) {
            var behaviors = this.behaviors;
            obj.isPrevent = false;
            obj.preventDefault = function () {
                obj.isPrevent = true;
            };
            obj.isStopPropagation = false;
            obj.stopPropagation = function () {
                obj.isStopPropagation = true;
            };
            obj.isStopNextBehavior = false;
            obj.stopNextBehavior = function () {
                obj.isStopNextBehavior = true;
            };
            obj.isStopNextWidget = false;
            obj.stopNextWidget = function () {
                obj.isStopNextWidget = true;
            };
            for (var i = 0; i < behaviors.length; i++) {
                if (obj.isStopNextBehavior) break;
                behaviors[i].dispatchEvent(name, obj);
            }
            return obj.isPrevent;
        }
    });
    App.Widget.transformToStr = function (list) {};
    App.Widget.strToTransform = function (str) {};
    App.Widget.strToShadow = function (str) {};
    App.Widget.getFillOrStroke = function (str) {};
    App.Widget.strToMarkerOffset = function (str) {
        return { x: 0, y: -20 };
    };
    App.Widget.getFontSize = function (str) {
        return 12;
    };
    App.Widget.extend = function (type, option) {
        function WidgetExtend(style, resources) {
            style = setDefault(style, option.style);
            if (resources) {
                if ("resourceKey" in this) {
                    for (var i = 0; i < this.resourceKey.length; i++) {
                        var obj = this.resourceKey[i], key = obj.key, ttype = obj.type;
                        if (resources[ttype]) {
                            var value = resources[ttype][style[key]];
                            if (value) style[key] = value;
                        }
                    }
                }
            }
            App.Widget.call(this, type, style);
        }

        WidgetExtend.prototype = create(App.Widget.prototype, option);
        return WidgetExtend;
    };

    App.WidgetBehavior = function (name, option) {
        BaseProto.call(this);
        this.uid = createUid();
        this.option = option || {};
        this.name = name;
        this.target = null;
        this.data = {};
    };
    App.WidgetBehavior.prototype = create(BaseProto.prototype, {
        constructor: App.WidgetBehavior,
        initialize: function () { },
        render: function (widget) { },
        toObject: function () {
            return { type: this.name, style: this.option };
        },
        destroy: function () {
            if (typeof this.dispose === "function") this.dispose();
            this.eventDispatcher.destroy();
            this.target = null;
            this.option = {};
            this.data = {};
        },
    });
    App.WidgetBehavior.extend = function (name, other) {
        function WidgetBehaviorExtend(option) {
            option = setDefault(option, other.option);
            App.WidgetBehavior.call(this, name, option);
        }
        WidgetBehaviorExtend.prototype = create(App.WidgetBehavior.prototype, other);
        return WidgetBehaviorExtend;
    }

    function Manager(list, feeback) {
        BaseProto.call(this)
        this.list = list;
        this.loadUnit = new LoadUnit(App.version);
        this.loadUnit.loadConfigs(list, feeback);
    }
    Manager.prototype = create(BaseProto.prototype, {});

    App.SceneManager = function (list, feeback) {
        Manager.call(this, list, feeback);
    };
    App.SceneManager.prototype = create(Manager.prototype, {
        constructor: App.SceneManager,
        getSceneByName: function (name) {
            return this.loadUnit.units[name];
        },
        addScene: function (name, style, viewer) {
            var SceneExt = this.getSceneByName(name);
            if (SceneExt) {
                var scene = new SceneExt(style);
                viewer.addScene(scene, true);
                scene._initialize();
                return scene;
            }
            return null;
        },
        defineScene: function (name, option) {
            var loadUnit = this.loadUnit;
            var script = document.querySelector("[data-name='" + name + "']");
            if (script) script.remove();
            else if (isNotWarn) console.warn("module: " + name + " is not required");

            var config = loadUnit._getConfigByName(name);
            loadUnit.units[name] = App.Scene.extend(config.name, option);
            loadUnit.dispatchEvent({ type: name, detail: { component: loadUnit.units[name], config: config } });
        }
    })
    App.WidgetManager = function (list, feeback) {
        Manager.call(this, list, feeback);
    };
    App.WidgetManager.prototype = create(Manager.prototype, {
        constructor: App.WidgetManager,
        getWidgetByName: function (name) {
            return this.loadUnit.units[name];
        },
        addWidget: function (type, style, scene, resource, name) {
            var WidgetExt = this.getWidgetByName(type);
            if (WidgetExt) {
                var widget = new WidgetExt(style, resource);
                if (name) widget.name = name;
                scene.addWidget(widget, true);
                widget.initialize();
                return widget;
            }
            return null;
        },
        defineWidget: function (name, option) {
            var loadUnit = this.loadUnit;
            var script = document.querySelector("[data-name='" + name + "']");
            console.log(name, script);
            if (script) script.remove();
            // else if (isNotWarn) console.warn("module: " + name + " is not required");

            var config = loadUnit._getConfigByName(name);
            loadUnit.units[name] = App.Widget.extend(config.name, option);
            loadUnit.dispatchEvent({ type: name, detail: { component: loadUnit.units[name], config: config } });
        }
    })
    App.WidgetBehaviorManager = function (list, feeback) {
        Manager.call(this, list, feeback);
    };
    App.WidgetBehaviorManager.prototype = create(Manager.prototype, {
        constructor: App.WidgetBehaviorManager,
        getBehaviorByName: function (name) {
            return this.loadUnit.units[name];
        },
        addBehavior: function (name, option, widget) {
            var BehaviorExt = this.getBehaviorByName(name);
            if (BehaviorExt) {
                var behavior = new BehaviorExt(option);
                widget.addBehavior(behavior, true);
                behavior.initialize();
                return true;
            }
            return false;
        },
        defineWidgetBehavior: function (name, option) {
            var loadUnit = this.loadUnit;
            var script = document.querySelector("[data-name='" + name + "']");
            if (script) script.remove();
            else if (isNotWarn) console.warn("module: " + name + " is not required");

            var config = loadUnit._getConfigByName(name);
            loadUnit.units[name] = App.WidgetBehavior.extend(config.name, option);
            loadUnit.dispatchEvent({ type: name, detail: { component: loadUnit.units[name], config: config } });
        }
    });

    App.TextAlignOption = {
        START: "start",
        CENTER: "center",
        END: "end"
    };
    App.VerticalAlignOption = {
        TOP: "top",
        MIDDLE: "middle",
        BOTTOM: "bottom"
    };
    App.PositionOption = {
        LEFTTOP: "lefttop",
        LEFTBOTTOM: "leftbottom",
        RIGHTTOP: "righttop",
        RIGHTBOTTOM: "rightbottom",
        CENTER: "center"
    };
    App.AxisDirectionOption = {
        LEFTRIGHT: "leftright",
        RIGHTLEFT: "rightleft",
        TOPBOTTOM: "topbottom",
        BOTTOMTOP: "bottomtop"
    };
    App.PieDirectionOption = {
        CW: "cw",
        CCW: "ccw"
    };

    App.globalImage = createDefaultCanvas();

    function createDefaultCanvas() {
        var canvas = document.createElement("canvas");
        canvas.width = 100;
        canvas.height = 100;
        var ctx = canvas.getContext("2d");
        var len = 8;
        ctx.fillStyle = "red";
        for (var i = 0; i < len; i++) {
            for (var j = 0; j < len; j++) {
                if ((i + j) % 2 == 1) {
                    var h = j * 100 / len, w = i * 100 / len;
                    ctx.fillRect(w, h, 100 / len, 100 / len);
                }
            }
        }
        return canvas;
    }
    

    App.ResourceManager = (function () {
        var getId = (function () {
            var id = 0;
            return function () {
                return "resource" + (id++);
            }
        })();
        var obj = {};
        return {
            set: function (resource) {
                var id = getId();
                obj[id] = resource;
                return id;
            },
            get: function (id) {
                return obj[id];
            },
            remove: function (id) {
                delete obj[id];
            },
            getAll: function () {
                var result = [];
                for (var key in obj) result.push(obj[key]);
                return result;
            }
        }
    })();

    return {
        Animation: Animation,
        App: App,
        Page: Page,
        Component: Component,
        PopUp: PopUp
    }
})();

var Animation = _STR.Animation, App = _STR.App, Page = _STR.Page, Component = _STR.Component, PopUp = _STR.PopUp;